root/build-tools/branching/create_branches_and_tags.sh
Scott Little ddd4d8792b fix several issues with branching tools
- force review method to 'default' if --bypass-gerrit is
selected.

- Allow local branch creation even if remote tracvking
branch is undetermined.

- allow git-utils to call into git-repo-utils to determine
the prefered remote rather than guessing without repo's help.

- allow git_local_branch to return 'HEAD' if there is
no better answer.

- git_remote must never return multiple remotes when
falling back to git_list_remotes.

- git_remote_branch must never return a tag.

Signed-off-by: Scott Little <scott.little@windriver.com>
Change-Id: Ib1ea9d4ced601e48ac6b5f41c629c9dae82e8108
2023-09-26 13:41:12 -04:00

503 lines
16 KiB
Bash
Executable File

#!/bin/bash
#
# Copyright (c) 2020-2022 Wind River Systems, Inc.
#
# SPDX-License-Identifier: Apache-2.0
#
#
# Create a git branch from the current heads on a subset of git projects
# within a manifest. A tag is also created to mark the commit where
# the branch was forked from. The subset of projects affected can be
# identified by project name or remote name.
#
# Optionally a new manifest (<branch>.xml) can be created that selects
# the new branch for the affected projects. As a further option,
# projects that are not branched can me 'locked down' within the new
# manifest by setting the sha of the current head as the revision.
#
# See also: push_branches_tags.sh
CREATE_BRANCHES_AND_TAGS_SH_DIR="$(dirname "$(readlink -f "${BASH_SOURCE[0]}" )" )"
source "${CREATE_BRANCHES_AND_TAGS_SH_DIR}/../git-repo-utils.sh"
usage () {
echo "create_branches_and_tags.sh --branch=<branch> [--tag=<tag>] <options>"
echo ""
echo " Create a branch on selected projects, and optionally update the manifest"
echo " to use the the new branch."
echo ""
echo " The branch name must be provided. The tag name can also be provided."
echo " If the tag is omitted, one is automativally generate by adding the"
echo " prefix 'v' to the branch name."
echo ""
echo "selection options:"
echo " [ --remotes=<remotes> ] [ --projects=<projects> ]"
echo ""
echo " Create a branch and a tag in all listed projects, and all"
echo " projects hosted by all listed remotes. Lists are comma separated."
echo ""
echo " [ --exclude-projects=<projects> ]"
echo ""
echo " Do not branch the list of projects. Used in conjunction with"
echo " --remotes to branch everything from a remote minus a few projects."
echo ""
echo "gitreview options:"
echo " Update any .gitreview files in branched projects."
echo ""
echo " [ --gitreview-host <host> ]"
echo " Set or update 'host' field."
echo " [ --gitreview-port <port> ]"
echo " Set or update 'port' field."
echo " [ --gitreview-project ]"
echo " Set or update 'project' field."
echo " [ --gitreview-default ]"
echo " Set or update 'defaultbranch' field."
echo " [ --safe-gerrit-host=<host> ]"
echo " allows one to specify host names of gerrit"
echo " servers that are safe to push reviews to."
echo ""
echo "manifest options:"
echo " [ --manifest ]"
echo " Modify the current repo manifest to specify the"
echo " new branch for all select remotes and projects."
echo " [ --manifest-file=<file.xml> ]"
echo " Override the manifest file to be updated."
echo " [ --default-revision ]"
echo " Set the default revision of the manifest."
echo " [ --hard-lock-down | --lockdown ]"
echo " All unselected projects get the current HEAD's"
echo " SHA set as the revision."
echo " [ --soft-lock-down ]"
echo " All unselected projects with an revision that"
echo " is unset, or 'master', get the current HEAD's sha"
echo " set as the revision."
echo " [ --lock-down-exclude-remotes=<remotes> ]"
echo " [ --lock-down-exclude-projects=<projects> ]"
echo " Exclude from lock-down these additional projects, and"
echo " all projects hosted by these additional remotes."
echo " Lists are comma separated."
echo ""
}
TEMP=$(getopt -o h --long remotes:,projects:,exclude-projects:,branch:,tag:,manifest,manifest-file:,lock-down,hard-lock-down,soft-lock-down,default-revision,gitreview-default,gitreview-project,gitreview-host:,gitreview-port:,safe-gerrit-host:,lock-down-exclude-remotes:,lock-down-exclude-projects:,help -n 'create_branches_and_tags.sh' -- "$@")
if [ $? -ne 0 ]; then
echo_stderr "ERROR: getopt failure"
usage
exit 1
fi
eval set -- "$TEMP"
HELP=0
MANIFEST=0
LOCK_DOWN=0
GITREVIEW_DEFAULT=0
GITREVIEW_PROJECT=0
GITREVIEW_HOST=""
GITREVIEW_PORT=""
GITREVIEW_CHANGE=0
SET_DEFAULT_REVISION=0
remotes=""
projects=""
exclude_projects=""
ld_exclude_remotes=""
ld_exclude_projects=""
branch=""
tag=""
manifest=""
new_manifest=""
repo_root_dir=""
safe_gerrit_hosts=()
while true ; do
case "$1" in
-h|--help) HELP=1 ; shift ;;
--remotes) remotes+=$(echo "$2 " | tr ',' ' '); shift 2;;
--projects) projects+=$(echo "$2 " | tr ',' ' '); shift 2;;
--exclude-projects) exclude_projects+=$(echo "$2 " | tr ',' ' '); shift 2;;
--lock-down-exclude-remotes) ld_exclude_remotes+=$(echo "$2 " | tr ',' ' '); shift 2;;
--lock-down-exclude-projects) ld_exclude_projects+=$(echo "$2 " | tr ',' ' '); shift 2;;
--branch) branch=$2; shift 2;;
--tag) tag=$2; shift 2;;
--manifest) MANIFEST=1 ; shift ;;
--manifest-file) repo_set_manifest_file "$2" ; shift 2 ;;
--lock-down) LOCK_DOWN=2 ; shift ;;
--hard-lock-down) LOCK_DOWN=2 ; shift ;;
--soft-lock-down) LOCK_DOWN=1 ; shift ;;
--default-revision) SET_DEFAULT_REVISION=1 ; shift ;;
--gitreview-default) GITREVIEW_DEFAULT=1 ; shift ;;
--gitreview-project) GITREVIEW_PROJECT=1 ; shift ;;
--gitreview-host) GITREVIEW_HOST=$2 ; shift 2;;
--gitreview-port) GITREVIEW_PORT=$2 ; shift 2;;
--safe-gerrit-host) safe_gerrit_hosts+=("$2") ; shift 2 ;;
--) shift ; break ;;
*) echo "unknown option $1"; usage; exit 1 ;;
esac
done
git_set_safe_gerrit_hosts "${safe_gerrit_hosts[@]}"
if [ $HELP -eq 1 ]; then
usage
exit 0
fi
if [ "$branch" == "" ] ; then
echo_stderr "ERROR: You must specify a branch"
usage
exit 1
fi
repo_root_dir=$(repo_root)
if [ $? -ne 0 ]; then
echo_stderr "Current directory is not managed by repo."
exit 1
fi
if [ $GITREVIEW_DEFAULT -ne 0 ] ||
[ $GITREVIEW_PROJECT -ne 0 ] ||
[ "$GITREVIEW_HOST" != "" ] ||
[ "$GITREVIEW_PORT" != "" ]; then
GITREVIEW_CHANGE=1
fi
update_field () {
local file=$1
local field=$2
local value=$3
local changed=0
if [ ! -f ${file} ]; then
echo "File ${file} not found"
return ${changed}
fi
if ! grep -q "^${field}=${value}$" ${file}; then
echo "Updating ${field} in ${file}"
if grep -q "^${field}=" ${file}; then
sed "s#\(${field}=\).*#\1${value}#" -i ${file}
else
echo "${field}=${value}" >> ${file}
fi
changed=1
else
echo "${field} in ${file} already set"
fi
return ${changed}
}
update_gitreview () {
local DIR=$1
local review_method=$2
local need_rm=0
local need_commit=0
local message="Update .gitreview for ${branch}"
local new_host=0
(
cd ${DIR} || exit 1
if [ ${GITREVIEW_CHANGE} -eq 1 ] && [ -f .gitreview ]; then
if [ "${GITREVIEW_HOST}" != "" ]; then
update_field ${PWD}/.gitreview host ${GITREVIEW_HOST} || need_commit=1
if [ ${need_commit} -ne 0 ]; then
new_host=1
fi
fi
if [ "${GITREVIEW_PORT}" != "" ]; then
update_field ${PWD}/.gitreview port ${GITREVIEW_PORT} || need_commit=1
fi
if [ ${GITREVIEW_PROJECT} -eq 1 ]; then
remote_url=$(git_repo_remote_url || git_remote_url)
pull_url=${remote_url}
path=$(url_path ${pull_url})
project=${path%.git}
update_field ${PWD}/.gitreview project ${project} || need_commit=1
fi
if [ ${GITREVIEW_DEFAULT} -eq 1 ]; then
update_field ${PWD}/.gitreview defaultbranch ${branch} || need_commit=1
fi
if [ $need_commit -eq 1 ]; then
echo_stderr "determinging git review method in ${DIR}"
[ -n "${review_method}" ] || review_method=$(git_repo_review_method)
if [ "${review_method}" == "gerrit" ] ; then
echo_stderr "running git review -s in ${DIR}"
with_retries -d 45 -t 15 -k 5 5 git review -s >&2
if [ $? != 0 ] ; then
if [ ${new_host} -eq 0 ]; then
echo_stderr "ERROR: 3: failed to setup git review in ${DIR}"
exit 1
fi
need_rm=1
message="Delete .gitreview for ${branch}"
fi
echo_stderr "finished git review -s in ${DIR}"
else
need_rm=1
message="Delete .gitreview for ${branch}"
fi
if [ ${need_rm} -eq 1 ]; then
git rm -f .gitreview
if [ $? != 0 ] ; then
echo_stderr "ERROR: failed to add .gitreview in ${DIR}"
exit 1
fi
else
git add .gitreview
if [ $? != 0 ] ; then
echo_stderr "ERROR: failed to add .gitreview in ${DIR}"
exit 1
fi
fi
git commit -s -m "${message}"
if [ $? != 0 ] ; then
echo_stderr "ERROR: failed to commit .gitreview in ${DIR}"
exit 1
fi
fi
fi
)
}
if [ $MANIFEST -eq 1 ]; then
manifest=$(repo_manifest $repo_root_dir)
if [ $? -ne 0 ]; then
echo_stderr "failed to find current manifest."
exit 1
fi
if [ ! -f $manifest ]; then
echo_stderr "manifest file missing '$manifest'."
exit 1
fi
# String the leading feature (f/) or branch (b/) from the
# branch name so we have a valid branch prefix
if [[ ${branch} =~ .*/.*$ ]]; then
manifest_prefix="$(basename ${branch})"
else
manifest_prefix="${branch}"
fi
new_manifest="$(dirname $manifest)/$manifest_prefix-$(basename $manifest)"
if [ -f $new_manifest ]; then
echo_stderr "branched manifest file already present '$new_manifest'."
exit 1
fi
fi
for project in $projects $exclude_projects $ld_exclude_projects; do
if ! repo_is_project $project; then
echo_stderr "Invalid project: $project"
echo_stderr "Valid projects are: $(repo_project_list | tr '\n' ' ')"
exit 1
fi
done
for remote in $remotes $ld_exclude_remotes; do
if ! repo_is_remote $remote; then
echo_stderr "Invalid remote: $remote"
echo_stderr "Valid remotes are: $(repo_remote_list | tr '\n' ' ')"
exit 1
fi
done
# Add projects from listed remotes
if [ "$remotes" != "" ]; then
projects+="$(repo_project_list $remotes | tr '\n' ' ')"
fi
if [ "$ld_exclude_remotes" != "" ]; then
ld_exclude_projects+="$(repo_project_list $ld_exclude_remotes | tr '\n' ' ')"
fi
# If no projects or remotes specified, process ALL projects
if [ "$projects" == "" ] && [ "$remotes" == "" ]; then
projects="$(repo_project_list)"
fi
if [ "$projects" != "" ] && [ "$exclude_projects" != "" ]; then
for project in $exclude_projects; do
projects=$(echo $projects | sed -e "s# $project # #" -e "s#^$project ##" -e "s# $project\$##" -e "s#^$project\$##")
done
fi
if [ "$projects" == "" ]; then
echo_stderr "No projects found"
exit 1
fi
echo "List of projects to be branched"
echo "==============================="
for project in $projects; do
echo $project
done
echo "==============================="
echo
# Provide a default tag name if not otherwise provided
if [ "$tag" == "" ]; then
tag="v$branch"
fi
echo "Finding subgits"
SUBGITS=$(repo forall $projects -c 'echo '"$repo_root_dir"'/$REPO_PATH')
# Go through all subgits and create the branch and tag if it does not already exist
echo "Applying branches and tags"
(
for subgit in $SUBGITS; do
(
cd $subgit
review_method=$(git_repo_review_method)
if [ -f .gitreview ] && [ "${review_method}" == "gerrit" ] ; then
with_retries -d 45 -t 15 -k 5 5 git review -s >&2
if [ $? != 0 ] ; then
echo_stderr "ERROR: 1: failed to setup git review in ${subgit}"
exit 1
fi
fi
remote=$(git_repo_remote)
if [ "${remote}" == "" ]; then
remote=$(git_remote)
if [ "${remote}" == "" ]; then
echo_stderr "ERROR: Failed to determine remote in ${subgit}"
exit 1
fi
fi
remote_branch=$(git_repo_remote_branch)
if [ "${remote_branch}" == "" ]; then
remote_branch=$(git_remote_branch)
fi
extra_args=""
if [ "${remote_branch}" != "" ]; then
extra_args="-t ${remote}/${remote_branch}"
fi
# check if destination branch already exists
branch_check=$(git branch -a --list $branch)
if [ -z "$branch_check" ]; then
echo "Creating branch $branch in $subgit"
echo " git checkout ${extra_args} -b $branch"
git checkout ${extra_args} -b $branch
if [ $? != 0 ] ; then
echo_stderr "ERROR: failed to create branch '$branch' in ${subgit}"
exit 1
fi
else
echo "Branch $branch already exists in $subgit"
git checkout $branch
fi
# check if destination tag already exists
tag_check=$(git tag -l $tag)
if [ -z "$tag_check" ]; then
echo "Creating tag $tag in ${subgit}"
git tag -s -m "Branch $branch" $tag
if [ $? != 0 ] ; then
echo "ERROR: failed to create tag '$tag' in ${subgit}"
exit 1
fi
else
echo "Tag '$tag' already exists in ${subgit}"
fi
update_gitreview ${subgit} || exit 1
) || exit 1
done
) || exit 1
if [ $MANIFEST -eq 1 ]; then
(
echo "Starting manifest update"
new_manifest_name=$(basename "${new_manifest}")
new_manifest_dir=$(dirname "${new_manifest}")
manifest_name=$(basename "${manifest}")
manifest_dir=$(dirname "${manifest}")
cd "${new_manifest_dir}" || exit 1
review_method=$(git_review_method)
if [ -f .gitreview ] && [ "${review_method}" == "gerrit" ] ; then
with_retries -d 45 -t 15 -k 5 5 git review -s >&2
if [ $? != 0 ] ; then
echo_stderr "ERROR: 2: failed to setup git review in ${new_manifest_dir}"
exit 1
fi
fi
branch_check=$(git branch -a --list $branch)
if [ ! -z "$branch_check" ]; then
echo "Branch $branch already exists in ${new_manifest_dir}"
exit 1
fi
tag_check=$(git tag -l $tag)
if [ ! -z "$tag_check" ]; then
echo "Tag '$tag' already exists in ${new_manifest_dir}"
exit 1
fi
remote=$(git_remote)
if [ "${remote}" == "" ]; then
echo_stderr "ERROR: Failed to determine remote in ${new_manifest_dir}"
exit 1
fi
remote_branch=$(git_remote_branch)
if [ "${remote_branch}" == "" ]; then
echo_stderr "ERROR: failed to determine remote branch in ${new_manifest_dir}"
exit 1
fi
echo "Creating branch '$branch' in ${new_manifest_dir}"
git checkout -t ${remote}/${remote_branch} -b $branch
if [ $? != 0 ] ; then
echo_stderr "ERROR: failed to create branch '$branch' in ${new_manifest_dir}"
exit 1
fi
echo "Creating tag '$tag' in ${new_manifest_dir}"
git tag -s -m "Branch $branch" $tag
if [ $? != 0 ] ; then
echo "ERROR: failed to create tag '$tag' in ${new_manifest_dir}"
exit 1
fi
update_gitreview ${manifest_dir} ${review_method} || exit 1
echo "Creating manifest ${new_manifest_name}"
manifest_set_revision "${manifest}" "${new_manifest}" "$branch" "${LOCK_DOWN}" "${SET_DEFAULT_REVISION}" "${projects// /,}" "${ld_exclude_projects// /,}" || exit 1
echo "Move manifest ${new_manifest_name}, overwriting ${manifest_name}"
\cp -f "${manifest}" "${manifest}.save"
\mv -f "${new_manifest}" "${manifest}"
echo "Committing ${manifest_name} in ${manifest_dir}"
git add ${manifest_name} || exit 1
git commit -s -m "Modified manifest ${manifest_name} for branch ${branch}"
if [ $? != 0 ] ; then
echo_stderr "ERROR: failed to commit new manifest ${manifest_name} in ${manifest_dir}"
exit 1
fi
) || exit 1
fi