ddd4d8792b
- 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
608 lines
14 KiB
Bash
Executable File
608 lines
14 KiB
Bash
Executable File
#!/bin/bash
|
|
|
|
#
|
|
# Copyright (c) 2018-2021 Wind River Systems, Inc.
|
|
#
|
|
# SPDX-License-Identifier: Apache-2.0
|
|
#
|
|
|
|
#
|
|
# A place for any functions relating to git, or the git hierarchy created
|
|
# by repo manifests.
|
|
#
|
|
|
|
GIT_UTILS_DIR="$(dirname "$(readlink -f "${BASH_SOURCE[0]}" )" )"
|
|
|
|
source ${GIT_UTILS_DIR}/utils.sh
|
|
|
|
# pseudo function pointer for git_remote (fallback) or git_repo_remote (preferred)
|
|
if [ -z git_remote_fp ]; then
|
|
git_remote_fp="git_remote"
|
|
fi
|
|
|
|
git_ctx_root_dir () {
|
|
dirname "${MY_REPO}"
|
|
}
|
|
|
|
#
|
|
# git_list <dir>:
|
|
# Return a list of git root directories found under <dir>
|
|
#
|
|
git_list () {
|
|
local DIR=${1}
|
|
|
|
find -L "${DIR}" -maxdepth 5 -type d -name '.git' -exec dirname {} \; | grep -v '[.]repo[/]repo$' | sort -V
|
|
}
|
|
|
|
|
|
# GIT_LIST: A list of root directories for all the gits under $MY_REPO/..
|
|
# as absolute paths.
|
|
export GIT_LIST=$(git_list "$(git_ctx_root_dir)")
|
|
|
|
|
|
# GIT_LIST_REL: A list of root directories for all the gits under $MY_REPO/..
|
|
# as relative paths.
|
|
export GIT_LIST_REL=$(for p in $GIT_LIST; do echo .${p#$(git_ctx_root_dir)}; done)
|
|
|
|
#
|
|
# git_list_containing_branch <dir> <branch>:
|
|
# Return a list of git root directories found under <dir> and
|
|
# having branch <branch>. The branch need not be current branch.
|
|
#
|
|
|
|
git_list_containing_branch () {
|
|
local DIR="${1}"
|
|
local BRANCH="${2}"
|
|
|
|
local d
|
|
for d in $(git_list "${DIR}"); do
|
|
(
|
|
cd "$d"
|
|
git branch --all | grep -q "$BRANCH"
|
|
if [ $? -eq 0 ]; then
|
|
echo "$d"
|
|
fi
|
|
)
|
|
done
|
|
}
|
|
|
|
|
|
#
|
|
# git_list_containing_tag <dir> <tag>:
|
|
# Return a list of git root directories found under <dir> and
|
|
# having tag <tag>.
|
|
#
|
|
|
|
git_list_containing_tag () {
|
|
local DIR="${1}"
|
|
local TAG="${2}"
|
|
|
|
local d
|
|
for d in $(git_list "${DIR}"); do
|
|
(
|
|
cd "$d"
|
|
git tag | grep -q "$TAG"
|
|
if [ $? -eq 0 ]; then
|
|
echo "$d"
|
|
fi
|
|
)
|
|
done
|
|
}
|
|
|
|
|
|
#
|
|
# git_root [<dir>]:
|
|
# Return the root directory of a git
|
|
# Note: symlinks are fully expanded.
|
|
#
|
|
|
|
git_root () {
|
|
local DIR="${1:-${PWD}}"
|
|
|
|
if [ ! -d "${DIR}" ]; then
|
|
DIR="$(dirname "${DIR}")"
|
|
fi
|
|
if [ ! -d "${DIR}" ]; then
|
|
echo_stderr "No such directory: ${DIR}"
|
|
return 1
|
|
fi
|
|
|
|
(
|
|
cd "${DIR}"
|
|
ROOT_DIR="$(git rev-parse --show-toplevel)" || exit 1
|
|
readlink -f "${ROOT_DIR}"
|
|
)
|
|
}
|
|
|
|
#
|
|
# git_list_tags [<dir>]:
|
|
# Return a list of all git tags.
|
|
# Either specify a directory <dir> in the git,
|
|
# or use current directory if unspecified.
|
|
#
|
|
|
|
git_list_tags () {
|
|
local DIR="${1:-${PWD}}"
|
|
|
|
(
|
|
cd "$DIR"
|
|
git tag
|
|
)
|
|
}
|
|
|
|
|
|
#
|
|
# git_list_branches [<dir>]:
|
|
# Return a list of all git branches.
|
|
# Non-local branches will be prefixed by 'remote/<remote-name>'
|
|
# Either specify a directory <dir> in the git,
|
|
# or use current directory if unspecified.
|
|
#
|
|
|
|
git_list_branches () {
|
|
local DIR="${1:-${PWD}}"
|
|
|
|
(
|
|
cd "$DIR"
|
|
git branch --list --all | sed 's#^..##'
|
|
)
|
|
}
|
|
|
|
|
|
#
|
|
# git_list_remote_branches <remote> [<dir>]:
|
|
# Return a list of all git branches defined for <remote>.
|
|
# Either specify a directory <dir> in the git,
|
|
# or use current directory if unspecified.
|
|
#
|
|
|
|
git_list_remote_branches () {
|
|
local REMOTE="${1}"
|
|
local DIR="${2:-${PWD}}"
|
|
|
|
(
|
|
cd "$DIR"
|
|
git branch --list --all "${REMOTE}/*" | sed "s#^.*/${REMOTE}/##"
|
|
)
|
|
}
|
|
|
|
|
|
#
|
|
# git_is_tag <tag> [<dir>]:
|
|
# Test if a <tag> is defined within a git.
|
|
# Either specify a directory <dir> in the git,
|
|
# or use current directory if unspecified.
|
|
#
|
|
|
|
git_is_tag () {
|
|
local TAG="${1}"
|
|
local DIR="${2:-${PWD}}"
|
|
|
|
# remove a trailing ^0 if present
|
|
TAG=${TAG%^0}
|
|
|
|
if [ "$TAG" == "" ]; then
|
|
return 1;
|
|
fi
|
|
|
|
(
|
|
cd "$DIR"
|
|
git show-ref ${TAG} | cut -d ' ' -f 2 | grep -q '^refs/tags/'
|
|
)
|
|
}
|
|
|
|
|
|
#
|
|
# git_is_local_branch <branch> [<dir>]:
|
|
# Test if a <branch> is defined locally within a git.
|
|
# Either specify a directory <dir> in the git,
|
|
# or use current directory if unspecified.
|
|
#
|
|
|
|
git_is_local_branch () {
|
|
local BRANCH="${1}"
|
|
local DIR="${2:-${PWD}}"
|
|
|
|
if [ "$BRANCH" == "" ]; then
|
|
return 1;
|
|
fi
|
|
|
|
(
|
|
cd "$DIR"
|
|
git show-ref ${BRANCH} | cut -d ' ' -f 2 | grep -q '^refs/heads/'
|
|
)
|
|
}
|
|
|
|
|
|
#
|
|
# git_is_remote_branch <branch> [<dir>]:
|
|
# Test if a <branch> is defined in any of the remotes of the git.
|
|
# The branche does NOT need to be prefixed by the remore name.
|
|
# Either specify a directory <dir> in the git,
|
|
# or use current directory if unspecified.
|
|
#
|
|
|
|
git_is_remote_branch () {
|
|
local BRANCH="${1}"
|
|
local DIR="${2:-${PWD}}"
|
|
|
|
if [ "$BRANCH" == "" ]; then
|
|
return 1;
|
|
fi
|
|
|
|
(
|
|
cd "$DIR"
|
|
git show-ref ${BRANCH} | cut -d ' ' -f 2 | grep -q '^refs/remotes/'
|
|
)
|
|
}
|
|
|
|
|
|
#
|
|
# git_is_branch <branch> [<dir>]:
|
|
# Test if a <branch> is defined in the git.
|
|
# The branch can be local or remote.
|
|
# Remote branches do NOT need to be prefixed by the remore name.
|
|
# Either specify a directory <dir> in the git,
|
|
# or use current directory if unspecified.
|
|
#
|
|
|
|
git_is_branch () {
|
|
local BRANCH="${1}"
|
|
local DIR="${2:-${PWD}}"
|
|
|
|
if [ "$BRANCH" == "" ]; then
|
|
return 1;
|
|
fi
|
|
|
|
git_is_local_branch ${BRANCH} "${DIR}" || git_is_remote_branch ${BRANCH} "${DIR}"
|
|
}
|
|
|
|
|
|
#
|
|
# git_is_ref <ref> [<dir>]:
|
|
# Test if a <ref> is a valid name for a commit.
|
|
# The reference can be a sha, tag, or branch.
|
|
# Remote branches must be prefixed by the remore name,
|
|
# as in <remote-name>/<branch> .
|
|
# Either specify a directory <dir> in the git,
|
|
# or use current directory if unspecified.
|
|
#
|
|
|
|
git_is_ref () {
|
|
local REF="${1}"
|
|
local DIR="${2:-${PWD}}"
|
|
|
|
if [ "$REF" == "" ]; then
|
|
return 1;
|
|
fi
|
|
|
|
# test "$(git cat-file -t ${REF})" == "commit"
|
|
local TYPE=""
|
|
TYPE="$(git cat-file -t ${REF} 2> /dev/null)" && test "${TYPE}" == "commit"
|
|
}
|
|
|
|
|
|
#
|
|
# git_is_sha <sha> [<dir>]:
|
|
# Test if a <sha> is defined in the git. The sha can be abreviated.
|
|
# Either specify a directory <dir> in the git,
|
|
# or use current directory if unspecified.
|
|
#
|
|
|
|
git_is_sha () {
|
|
local SHA="${1}"
|
|
local DIR="${2:-${PWD}}"
|
|
|
|
if [ "$SHA" == "" ]; then
|
|
return 1;
|
|
fi
|
|
|
|
git_is_ref ${SHA} "${DIR}" && ! ( git_is_branch ${SHA} "${DIR}" || git_is_tag ${SHA} "${DIR}")
|
|
}
|
|
|
|
|
|
#
|
|
# git_ref_type <ref> [<dir>]:
|
|
# Determine the type of the git reference <ref>.
|
|
# The result, via stdout, will be one of ("sha", "tag", "branch" or "invalid")
|
|
# Remote branches do NOT need to be prefixed by the remore name.
|
|
# Either specify a directory <dir> in the git,
|
|
# or use current directory if unspecified.
|
|
|
|
git_ref_type () {
|
|
local REF="${1}"
|
|
local DIR="${2:-${PWD}}"
|
|
|
|
if git_is_branch ${REF} ${DIR}; then
|
|
echo 'branch'
|
|
return 0
|
|
fi
|
|
if git_is_tag ${REF} ${DIR}; then
|
|
echo 'tag'
|
|
return 0
|
|
fi
|
|
if git_is_sha ${REF} ${DIR}; then
|
|
echo 'sha'
|
|
return 0
|
|
fi
|
|
echo 'invalid'
|
|
return 1
|
|
}
|
|
|
|
#
|
|
#
|
|
# git_context:
|
|
# Returns a bash script that can be used to recreate the current git context,
|
|
#
|
|
# Note: all paths are relative to $MY_REPO/..
|
|
#
|
|
|
|
git_context () {
|
|
(
|
|
cd $(git_ctx_root_dir)
|
|
|
|
local d
|
|
for d in $GIT_LIST_REL; do
|
|
(
|
|
cd ${d}
|
|
echo -n "(cd ${d} && git checkout -f "
|
|
echo "$(git rev-list HEAD -1))"
|
|
)
|
|
done
|
|
)
|
|
}
|
|
|
|
#
|
|
# git_test_context <context>:
|
|
#
|
|
# Test if all commits referenced in the context are present
|
|
# in the history of the gits in their current checkout state.
|
|
#
|
|
# Returns: 0 = context is present in git history
|
|
# 1 = At least one element of context is not present
|
|
# 2 = error
|
|
#
|
|
git_test_context () {
|
|
local context="$1"
|
|
local query=""
|
|
local target_hits=0
|
|
local actual_hits=0
|
|
|
|
if [ ! -f "$context" ]; then
|
|
return 2
|
|
fi
|
|
|
|
query=$(mktemp "/tmp/git_test_context_XXXXXX")
|
|
if [ "$query" == "" ]; then
|
|
return 2
|
|
fi
|
|
|
|
# Transform a checkout context into a query that prints
|
|
# all the commits that are found in the git history.
|
|
#
|
|
# Limit search to last 500 commits in the interest of speed.
|
|
# I don't expect to be using contexts more than a few weeks old.
|
|
cat "$context" | \
|
|
sed -e "/\.repo\/repo/d" \
|
|
-e "s#checkout -f \([a-e0-9]*\)#rev-list --max-count=500 HEAD | \
|
|
grep \1#" > $query
|
|
|
|
target_hits=$(cat "$context" | grep -v '[.]repo[/]repo ' | wc -l)
|
|
actual_hits=$(cd $(git_ctx_root_dir); source $query 2> /dev/null | wc -l)
|
|
\rm $query
|
|
|
|
if [ $actual_hits -eq $target_hits ]; then
|
|
return 0
|
|
fi
|
|
|
|
return 1
|
|
}
|
|
|
|
git_local_branch () {
|
|
local result=""
|
|
local sha=""
|
|
|
|
# Older gits don't support this
|
|
result=$(git branch --show-current 2> /dev/null)
|
|
if [ "$result" != "" ]; then
|
|
echo $result
|
|
return 0
|
|
fi
|
|
|
|
# Might not work if detached and there are local commits
|
|
result=$(git branch | grep '^[*] ' | cut -b 3- | grep -v HEAD)
|
|
if [ "$result" != "" ]; then
|
|
echo $result
|
|
return 0
|
|
fi
|
|
|
|
# Find 'nearest' local branch that we detached from and/or added commits to
|
|
sha=$(git rev-parse HEAD)
|
|
while [ $? -eq 0 ]; do
|
|
result=$(git show-ref --head | grep -e "$sha refs/heads/" | sed "s#$sha refs/heads/##" | head -n 1)
|
|
if [ "$result" != "" ]; then
|
|
echo $result
|
|
return 0
|
|
fi
|
|
|
|
sha=$(git rev-parse $sha^ 2> /dev/null)
|
|
done
|
|
|
|
# This used to work on older git versions
|
|
result=$(git name-rev --name-only HEAD)
|
|
if [ "$result" == "" ] || [ "$result" == "undefined" ]; then
|
|
echo "HEAD"
|
|
return 1
|
|
fi
|
|
|
|
# Handle the case where a tag is returned by looking at the parent.
|
|
# This weird case when a local commit is tagged and we were in
|
|
# detached head state, or on 'default' branch.
|
|
while git_is_tag $result; do
|
|
result=$(git name-rev --name-only $result^1 )
|
|
if [ "$result" == "" ] || [ "$result" == "undefined" ]; then
|
|
echo "HEAD"
|
|
return 1
|
|
fi
|
|
done
|
|
|
|
echo $result
|
|
}
|
|
|
|
git_list_remotes () {
|
|
git remote | grep -v gerrit
|
|
}
|
|
|
|
|
|
git_remote () {
|
|
local DIR="${1:-${PWD}}"
|
|
|
|
(
|
|
cd ${DIR}
|
|
local_branch=$(git_local_branch) || return 1
|
|
|
|
# Return remote of current local branch, else default remote.
|
|
git config branch.${local_branch}.remote || git_list_remotes | head -n 1
|
|
)
|
|
}
|
|
|
|
git_remote_url () {
|
|
local remote=""
|
|
remote=$(git_remote) || return 1
|
|
git config remote.$remote.url
|
|
}
|
|
|
|
git_remote_branch () {
|
|
local local_branch=""
|
|
local sha=""
|
|
local remote=""
|
|
|
|
# Our best bet is if the git config shows the local
|
|
# branch is tracking a remote branch.
|
|
local_branch=$(git_local_branch) || return 1
|
|
git config branch.${local_branch}.merge | grep -v '^refs/tags/' | sed 's#^refs/heads/##'
|
|
if [ ${PIPESTATUS[0]} -eq 0 ]; then
|
|
return 0
|
|
fi
|
|
|
|
# Before we can select a remote branch, we need to know which remote.
|
|
remote=$($git_remote_fp)
|
|
if [ $? -ne 0 ] || [ "$remote" == "" ]; then
|
|
return 1
|
|
fi
|
|
|
|
# Find 'nearest' remote branch that we detached from and/or added commits to
|
|
sha=$(git rev-parse HEAD)
|
|
while [ $? -eq 0 ]; do
|
|
result=$(git show-ref --head | grep -e "$sha refs/remotes/$remote/" | sed "s#$sha refs/remotes/$remote/##" | head -n 1)
|
|
if [ "$result" != "" ]; then
|
|
echo $result
|
|
return 0
|
|
fi
|
|
|
|
sha=$(git rev-parse $sha^ 2> /dev/null)
|
|
done
|
|
return 1
|
|
}
|
|
|
|
# Usage: git_set_safe_gerrit_hosts HOST1 HOST2...
|
|
# Set the host names that are safe to push reviews to
|
|
GIT_SAFE_GERRIT_HOSTS=()
|
|
git_set_safe_gerrit_hosts() {
|
|
GIT_SAFE_GERRIT_HOSTS=()
|
|
while [ "$#" -gt 0 ] ; do
|
|
GIT_SAFE_GERRIT_HOSTS+=("$1")
|
|
shift
|
|
done
|
|
}
|
|
|
|
# Usage: git_match_safe_gerrit_host HOSTNAME
|
|
# Return true if given host name is safe to push reviews to
|
|
# You have to call git_set_safe_gerrit_hosts() first
|
|
git_match_safe_gerrit_host() {
|
|
local review_host="$1"
|
|
local host
|
|
for host in "${GIT_SAFE_GERRIT_HOSTS[@]}" ; do
|
|
if [ "${review_host}" == "${host}" ]; then
|
|
return 0
|
|
fi
|
|
done
|
|
return 1
|
|
}
|
|
|
|
git_review_method () {
|
|
local GIT_DIR
|
|
local url="" host=""
|
|
url=$(git_remote_url) || exit 1
|
|
if [[ "${url}" =~ "/git.starlingx.io/" || "${url}" =~ "/opendev.org/" ]]; then
|
|
echo 'gerrit'
|
|
return 0
|
|
fi
|
|
|
|
GIT_DIR=$(git_root ${PWD}) || return 1
|
|
|
|
if [ ! -f ${GIT_DIR}/.gitreview ]; then
|
|
# No .gitreview file
|
|
echo 'default'
|
|
return 0
|
|
fi
|
|
|
|
if ! grep -q '\[gerrit\]' ${GIT_DIR}/.gitreview; then
|
|
# .gitreview file has no gerrit entry
|
|
echo 'default'
|
|
return 0
|
|
fi
|
|
|
|
review_host="$(grep host= ${GIT_DIR}/.gitreview | sed 's#^host=##' | head -n 1)"
|
|
if git_match_safe_gerrit_host "${review_host}" ; then
|
|
echo "gerrit"
|
|
return 0
|
|
fi
|
|
echo "default"
|
|
|
|
}
|
|
|
|
git_review_url () {
|
|
local method=""
|
|
method=$(git_review_method)
|
|
if [ "${method}" == "gerrit" ]; then
|
|
git config remote.gerrit.url
|
|
if [ $? -ne 0 ]; then
|
|
# Perhaps we need to run git review -s' and try again
|
|
with_retries -d 45 -t 15 -k 5 5 git review -s >&2 || return 1
|
|
git config remote.gerrit.url
|
|
fi
|
|
else
|
|
git_remote_url
|
|
fi
|
|
}
|
|
|
|
git_review_remote () {
|
|
local method=""
|
|
method=$(git_review_method)
|
|
if [ "${method}" == "gerrit" ]; then
|
|
git config remote.gerrit.url > /dev/null
|
|
if [ $? -ne 0 ]; then
|
|
# Perhaps we need to run git review -s' and try again
|
|
with_retries -d 45 -t 15 -k 5 5 git review -s >&2 || return 1
|
|
git config remote.gerrit.url > /dev/null || return 1
|
|
fi
|
|
echo "gerrit"
|
|
else
|
|
git_remote
|
|
fi
|
|
}
|
|
|
|
git_set_push_url_with_access_token () {
|
|
local remote="$1"
|
|
local access_token="$2"
|
|
local push_url=""
|
|
|
|
# Get url of remote. Insert 'access_token@ into the url
|
|
push_url=$(git remote get-url ${remote} | sed "s#://#://${access_token}@#")
|
|
if [ $? != 0 ] || [ "$push_url" == "" ] ; then
|
|
return 1
|
|
fi
|
|
git remote set-url --push ${remote} ${push_url}
|
|
}
|