Skip to content
Open
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
186 changes: 119 additions & 67 deletions src/all-hooks/package-submodule-blacklist
Original file line number Diff line number Diff line change
@@ -1,67 +1,119 @@
#!/bin/bash
#
# A git server hook that rejects pushes containing blacklisted package submodules.

set -euo pipefail

BLACKLIST_FILE="_obs/hooks/package-submodule-blacklist.txt"

hook_name_printed=false

# Read the old and new commit SHAs from stdin.
while read -r old_rev new_rev ref_name; do

# Skip on branch deletion.
[[ "$new_rev" =~ ^0+$ ]] && continue

# Handle branch creation.
range="${old_rev}..${new_rev}"
blacklist_rev="$old_rev"
if [[ "$old_rev" =~ ^0+$ ]]; then
range="$new_rev"
blacklist_rev="$new_rev"
fi

# Skip if blacklist file does not exist in this revision.
git cat-file -e "${blacklist_rev}:${BLACKLIST_FILE}" 2>/dev/null || continue

[ "$hook_name_printed" = false ] && echo "Running hook: $(basename $0)" && hook_name_printed=true

# Load blacklist file into an in-memory set.
declare -A blacklist_names=()
while IFS= read -r name; do
[ -n "$name" ] && blacklist_names["$name"]=1
done < <(git show "${blacklist_rev}:${BLACKLIST_FILE}" 2>/dev/null || true)
[ "${#blacklist_names[@]}" -eq 0 ] && continue

# Limit range to only new commits relative to repository history.
merge_base=$(git merge-base "${new_rev}" $(git for-each-ref --format='%(objectname)') 2>/dev/null | head -n1 || true)
[ -n "$merge_base" ] && range="${merge_base}..${new_rev}"

# Walk each introduced commit and check changed submodule entries.
declare -A matches=()
while read -r commit; do
while IFS=$'\t' read -r meta path _rest; do
read -r _old_mode new_mode _old_sha _new_sha status <<< "${meta#:}"
[ "$new_mode" = "160000" ] || continue
case "$status" in
D*) continue ;;
esac

name=${path##*/}
[ -n "${blacklist_names[$name]+x}" ] && matches["$name"]=1
done < <(git diff-tree --root -r --no-commit-id "$commit")
done < <(git rev-list "${range}")

if [ "${#matches[@]}" -gt 0 ]; then
echo "Commit/range: ${range}" >&2
# Report unique blacklisted submodules found.
printf '%s\n' "${!matches[@]}" | sort | while read -r name; do
echo "ERROR: Blacklisted submodule: ${name}" >&2
done
exit 1
fi

done

exit 0
#!/usr/bin/python3

import os
import subprocess
import sys

if os.environ.get("PACKAGE_SUBMODULE_BLACKLIST_SKIP", None):
sys.exit(0)

from osc.git_scm.store import GitStore
from osc.gitea_api.git import Git


BLACKLIST_FILE = ".git-workflow/hooks/package-submodule-blacklist.txt"


def check_revision(rev: str) -> bool:
"""
Check whether a revision contains blacklisted package submodules.
Return True on success, False on failure.
"""
success = True

# we always need to instantiate a new GitStore because it uses ref to load the correct _manifest
store = GitStore(".", ref=rev, check=False)
git = store._git

blacklist_names = set()
if git.is_bare:
try:
data = git._run_git(["cat-file", "blob", f"{rev}:{BLACKLIST_FILE}"], mute_stderr=True)
blacklist_names = {i.strip() for i in data.splitlines() if i.strip()}
except subprocess.CalledProcessError:
# blacklist file doesn't exist -> skip the check, report success
return success
else:
try:
with open(BLACKLIST_FILE, "r", encoding="utf-8") as f:
blacklist_names = {i.strip() for i in f.readlines() if i.strip()}
except FileNotFoundError:
# blacklist file doesn't exist -> skip the check, report success
return success

manifest = store.manifest
if manifest is not None:
if git.is_bare:
directories = git.ls_tree(rev)
directories = [i["path"] for i in directories if i["file_type"] in ("directory", "submodule")]
package_paths = manifest.get_package_paths_bare_git(store.topdir, directories=directories)
else:
package_paths = manifest.get_package_paths(store.topdir, relative=True)
else:
directories = git.ls_tree(rev)
package_paths = [i["path"] for i in directories if i["file_type"] in ("directory", "submodule")]

submodules = store._git.get_submodules(ref=rev)
submodule_names = set()
for key, value in submodules.items():
if value["path"] not in package_paths:
continue
submodule_names.add(os.path.basename(key))

blacklist_names = {os.path.basename(i) for i in blacklist_names}

matches = sorted(submodule_names & blacklist_names)
if matches:
print(f"Error in commit {rev}")
print("The following package submodule names are blacklisted: ")
for name in matches:
print(f" - {name}")
success = False
return success


def parse_prepush_githook_stdin():
"""
Returns [(local_ref, local_sha, remote_ref, remote_sha)] from stdin.
Standard for pre-push hooks.
"""
result = []
for line in sys.stdin:
parts = line.split()
if len(parts) == 4:
# pre-push: <local_ref> <local_sha> <remote_ref> <remote_sha>
local_ref, local_sha, remote_ref, remote_sha = parts
result.append((local_ref, local_sha, remote_ref, remote_sha))
else:
raise RuntimeError(f"Unexpected line in githook stdin: {line}")
return result


def main():
git = Git(".")
seen = set()
success = True

# poor man's detection that we're in a pre-receive hook
is_pre_receive = bool(git.is_bare)

if is_pre_receive:
rev_list = [(i[0], i[1]) for i in git.parse_githook_stdin()]
else:
rev_list = [(i[3], i[1]) for i in parse_prepush_githook_stdin()]

for old_rev, new_rev in rev_list:
# we want to enable 'not_all' only in the pre-receive hook
commits = git.get_commit_range(old_rev, new_rev, not_all=is_pre_receive)
for sha in commits:
if sha in seen:
continue
success &= check_revision(sha)
seen.add(sha)

if not success:
sys.exit(1)


if __name__ == "__main__":
main()
56 changes: 0 additions & 56 deletions src/all-hooks/package-submodule-blacklist-client-side

This file was deleted.

1 change: 1 addition & 0 deletions src/git-obs/pre-push.d/package-submodule-blacklist

This file was deleted.

Loading