root/build-tools/git-utils.sh

482 lines
10 KiB
Bash
Executable File

#!/bin/bash
#
# Copyright (c) 2018 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.
#
echo_stderr ()
{
echo "$@" >&2
}
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 {} \; | 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
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 "s#checkout -f \([a-e0-9]*\)#rev-list --max-count=500 HEAD | \
grep \1#" > $query
target_hits=$(cat "$context" | wc -l)
actual_hits=$(cd $(git_ctx_root_dir); source $query | wc -l)
\rm $query
if [ $actual_hits -eq $target_hits ]; then
return 0
fi
return 1
}
git_local_branch () {
local result=""
result=$(git name-rev --name-only HEAD)
if [ "$result" == "" ] || [ "$result" == "undefined" ]; then
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
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
)
}
git_remote_url () {
local remote=""
remote=$(git_remote) || return 1
git config remote.$remote.url
}
git_remote_branch () {
local local_branch=""
local_branch=$(git_local_branch) || return 1
git config branch.${local_branch}.merge | sed 's#^refs/heads/##'
}
git_review_method () {
local url=""
url=$(git_remote_url) || exit 1
if [[ "${url}" =~ "/git.starlingx.io/" || "${url}" =~ "/opendev.org/" ]]; then
echo 'gerrit'
else
echo 'default'
fi
}
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
git review -s > /dev/null || 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
git review -s > /dev/null || return 1
git config remote.gerrit.url > /dev/null || return 1
fi
echo "gerrit"
else
git_remote
fi
}