You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
gentoo-overlay/eclass/git-r3.eclass

1189 lines
36 KiB

# Copyright 1999-2024 Gentoo Authors
# Distributed under the terms of the GNU General Public License v2
# @ECLASS: git-r3.eclass
# @MAINTAINER:
# Michał Górny <mgorny@gentoo.org>
# @SUPPORTED_EAPIS: 6 7 8
# @BLURB: Eclass for fetching and unpacking git repositories.
# @DESCRIPTION:
# Third generation eclass for easing maintenance of live ebuilds using
# git as remote repository.
# @ECLASS_VARIABLE: EGIT_LFS
# @PRE_INHERIT
# @DEFAULT_UNSET
# @DESCRIPTION:
# If set, git lfs support will be enabled.
# Set before inheriting this eclass.
# @ECLASS_VARIABLE: _NUM_LFS_FILTERS_FOUND
# @INTERNAL
# @DEFAULT_UNSET
# @DESCRIPTION:
# This is used to provide QA warnings if a repo has git lfs filters
# defined but EGIT_LFS is not turned on and vice versa.
# If non-empty, then the repo likely needs EGIT_LFS to clone properly.
case ${EAPI} in
6|7|8) ;;
*) die "${ECLASS}: EAPI ${EAPI:-0} not supported" ;;
esac
if [[ -z ${_GIT_R3_ECLASS} ]]; then
_GIT_R3_ECLASS=1
PROPERTIES+=" live"
if [[ ${EAPI} != 6 ]]; then
BDEPEND=">=dev-vcs/git-1.8.2.1[curl]"
[[ ${EGIT_LFS} ]] && BDEPEND+=" dev-vcs/git-lfs"
else
DEPEND=">=dev-vcs/git-1.8.2.1[curl]"
[[ ${EGIT_LFS} ]] && DEPEND+=" dev-vcs/git-lfs"
fi
# @ECLASS_VARIABLE: EGIT_CLONE_TYPE
# @USER_VARIABLE
# @DESCRIPTION:
# Type of clone that should be used against the remote repository.
# This can be either of: 'mirror', 'single', 'shallow'.
#
# This is intended to be set by user in make.conf. Ebuilds are supposed
# to set EGIT_MIN_CLONE_TYPE if necessary instead.
#
# The 'mirror' type clones all remote branches and tags with complete
# history and all notes. EGIT_COMMIT can specify any commit hash.
# Upstream-removed branches and tags are purged from the local clone
# while fetching. This mode is suitable for cloning the local copy
# for development or hosting a local git mirror. However, clones
# of repositories with large diverged branches may quickly grow large.
#
# The 'single+tags' type clones the requested branch and all tags
# in the repository. All notes are fetched as well. EGIT_COMMIT
# can safely specify hashes throughout the current branch and all tags.
# No purging of old references is done (if you often switch branches,
# you may need to remove stale branches yourself). This mode is intended
# mostly for use with broken git servers such as Google Code that fail
# to fetch tags along with the branch in 'single' mode.
#
# The 'single' type clones only the requested branch or tag. Tags
# referencing commits throughout the branch history are fetched as well,
# and all notes. EGIT_COMMIT can safely specify only hashes
# in the current branch. No purging of old references is done (if you
# often switch branches, you may need to remove stale branches
# yourself). This mode is suitable for general use.
#
# The 'shallow' type clones only the newest commit on requested branch
# or tag. EGIT_COMMIT can only specify tags, and since the history is
# unavailable calls like 'git describe' will not reference prior tags.
# No purging of old references is done. This mode is intended mostly for
# embedded systems with limited disk space.
: "${EGIT_CLONE_TYPE:=single}"
# @ECLASS_VARIABLE: EGIT_MIN_CLONE_TYPE
# @DESCRIPTION:
# 'Minimum' clone type supported by the ebuild. Takes same values
# as EGIT_CLONE_TYPE. When user sets a type that's 'lower' (that is,
# later on the list) than EGIT_MIN_CLONE_TYPE, the eclass uses
# EGIT_MIN_CLONE_TYPE instead.
#
# This variable is intended to be used by ebuilds only. Users are
# supposed to set EGIT_CLONE_TYPE instead.
#
# A common case is to use 'single' whenever the build system requires
# access to full branch history, or 'single+tags' when Google Code
# or a similar remote is used that does not support shallow clones
# and fetching tags along with commits. Please use sparingly, and to fix
# fatal errors rather than 'non-pretty versions'.
: "${EGIT_MIN_CLONE_TYPE:=shallow}"
# @ECLASS_VARIABLE: EGIT_LFS_CLONE_TYPE
# @USER_VARIABLE
# @DESCRIPTION:
# Type of lfs clone that should be used against the remote repository.
# This can be either of: 'mirror', 'single', 'shallow'.
#
# This works a bit differently than EGIT_CLONE_TYPE.
#
# The 'mirror' type clones all LFS files that is available from the
# cloned repo. Is is mostly useful for backup or rehosting purposes as
# the disk usage will be excessive.
#
# The 'single' type clones only the LFS files from the current commit.
# However unlike 'shallow', it will not cleanup stale LFS files.
#
# The 'shallow' type clones only the LFS files from the current commit.
# LFS files that are not referenced by the current commit and more than
# a few days old will be automatically removed to save disk space.
# This is the recommended mode for LFS repos to prevent excessive disk
# usage.
: "${EGIT_LFS_CLONE_TYPE:=shallow}"
# @ECLASS_VARIABLE: EVCS_STORE_DIRS
# @OUTPUT_VARIABLE
# @DESCRIPTION:
# Record of names of all the repositories directories being cloned in the git3_src.
# This is useful in the case of ebuild that fetch multiple repos and
# it would be used by eclean to clean them up.
EVCS_STORE_DIRS=()
# @ECLASS_VARIABLE: EGIT3_STORE_DIR
# @USER_VARIABLE
# @DEFAULT_UNSET
# @DESCRIPTION:
# Storage directory for git sources.
#
# This is intended to be set by user in make.conf. Ebuilds must not set
# it.
#
# EGIT3_STORE_DIR=${DISTDIR}/git3-src
# @ECLASS_VARIABLE: EGIT_MIRROR_URI
# @DEFAULT_UNSET
# @DESCRIPTION:
# 'Top' URI to a local git mirror. If specified, the eclass will try
# to fetch from the local mirror instead of using the remote repository.
#
# The mirror needs to follow EGIT3_STORE_DIR structure. The directory
# created by eclass can be used for that purpose.
#
# Example:
# @CODE
# EGIT_MIRROR_URI="git://mirror.lan/"
# @CODE
# @ECLASS_VARIABLE: EGIT_REPO_URI
# @REQUIRED
# @DESCRIPTION:
# URIs to the repository, e.g. https://foo. If multiple URIs are
# provided, the eclass will consider the remaining URIs as fallbacks
# to try if the first URI does not work. For supported URI syntaxes,
# read the manpage for git-clone(1).
#
# URIs should be using https:// whenever possible. http:// and git://
# URIs are completely insecure and their use (even if only as
# a fallback) renders the ebuild completely vulnerable to MITM attacks.
#
# Can be a whitespace-separated list or an array.
#
# Example:
# @CODE
# EGIT_REPO_URI="https://a/b.git https://c/d.git"
# @CODE
# @ECLASS_VARIABLE: EVCS_OFFLINE
# @DEFAULT_UNSET
# @DESCRIPTION:
# If non-empty, this variable prevents any online operations.
# @ECLASS_VARIABLE: EVCS_UMASK
# @DEFAULT_UNSET
# @DESCRIPTION:
# Set this variable to a custom umask. This is intended to be set by
# users. By setting this to something like 002, it can make life easier
# for people who do development as non-root (but are in the portage
# group), and then switch over to building with FEATURES=userpriv.
# Or vice-versa. Shouldn't be a security issue here as anyone who has
# portage group write access already can screw the system over in more
# creative ways.
# @ECLASS_VARIABLE: EGIT_BRANCH
# @DEFAULT_UNSET
# @DESCRIPTION:
# The branch name to check out. If unset, the upstream default (HEAD)
# will be used.
# @ECLASS_VARIABLE: EGIT_COMMIT
# @DEFAULT_UNSET
# @DESCRIPTION:
# The tag name or commit identifier to check out. If unset, newest
# commit from the branch will be used. Note that if set to a commit
# not on HEAD branch, EGIT_BRANCH needs to be set to a branch on which
# the commit is available.
# @ECLASS_VARIABLE: EGIT_COMMIT_DATE
# @DEFAULT_UNSET
# @DESCRIPTION:
# Attempt to check out the repository state for the specified timestamp.
# The date should be in format understood by 'git rev-list'. The commits
# on EGIT_BRANCH will be considered.
#
# The eclass will select the last commit with commit date preceding
# the specified date. When merge commits are found, only first parents
# will be considered in order to avoid switching into external branches
# (assuming that merges are done correctly). In other words, each merge
# will be considered alike a single commit with date corresponding
# to the merge commit date.
# @ECLASS_VARIABLE: EGIT_CHECKOUT_DIR
# @DEFAULT_UNSET
# @DESCRIPTION:
# The directory to check the git sources out to.
#
# EGIT_CHECKOUT_DIR=${WORKDIR}/${P}
# @ECLASS_VARIABLE: EGIT_SUBMODULES
# @DEFAULT_UNSET
# @DESCRIPTION:
# An array of inclusive and exclusive wildcards on submodule names,
# stating which submodules are fetched and checked out. Exclusions
# start with '-', and exclude previously matched submodules.
#
# If unset, all submodules are enabled. Empty list disables all
# submodules. In order to use an exclude-only list, start the array
# with '*'.
#
# Remember that wildcards need to be quoted in order to prevent filename
# expansion.
#
# Examples:
# @CODE
# # Disable all submodules
# EGIT_SUBMODULES=()
#
# # Include only foo and bar
# EGIT_SUBMODULES=( foo bar )
#
# # Use all submodules except for test-* but include test-lib
# EGIT_SUBMODULES=( '*' '-test-*' test-lib )
# @CODE
# @FUNCTION: _git-r3_env_setup
# @INTERNAL
# @DESCRIPTION:
# Set the eclass variables as necessary for operation. This can involve
# setting EGIT_* to defaults or ${PN}_LIVE_* variables.
_git-r3_env_setup() {
debug-print-function ${FUNCNAME} "$@"
# check the clone type
case "${EGIT_CLONE_TYPE}" in
mirror|single+tags|single|shallow)
;;
*)
die "Invalid EGIT_CLONE_TYPE=${EGIT_CLONE_TYPE}"
esac
case "${EGIT_MIN_CLONE_TYPE}" in
shallow)
;;
single)
if [[ ${EGIT_CLONE_TYPE} == shallow ]]; then
einfo "git-r3: ebuild needs to be cloned in 'single' mode, adjusting"
EGIT_CLONE_TYPE=single
fi
;;
single+tags)
if [[ ${EGIT_CLONE_TYPE} == shallow || ${EGIT_CLONE_TYPE} == single ]]; then
einfo "git-r3: ebuild needs to be cloned in 'single+tags' mode, adjusting"
EGIT_CLONE_TYPE=single+tags
fi
;;
mirror)
if [[ ${EGIT_CLONE_TYPE} != mirror ]]; then
einfo "git-r3: ebuild needs to be cloned in 'mirror' mode, adjusting"
EGIT_CLONE_TYPE=mirror
fi
;;
*)
die "Invalid EGIT_MIN_CLONE_TYPE=${EGIT_MIN_CLONE_TYPE}"
esac
if [[ ${EGIT_SUBMODULES[@]+1} && $(declare -p EGIT_SUBMODULES) != "declare -a"* ]]
then
die 'EGIT_SUBMODULES must be an array.'
fi
local esc_pn livevar
esc_pn=${PN//[-+]/_}
[[ ${esc_pn} == [0-9]* ]] && esc_pn=_${esc_pn}
# note: deprecated, use EGIT_OVERRIDE_* instead
livevar=${esc_pn}_LIVE_REPO
EGIT_REPO_URI=${!livevar-${EGIT_REPO_URI}}
[[ ${!livevar} ]] \
&& ewarn "Using ${livevar}, no support will be provided"
livevar=${esc_pn}_LIVE_BRANCH
EGIT_BRANCH=${!livevar-${EGIT_BRANCH}}
[[ ${!livevar} ]] \
&& ewarn "Using ${livevar}, no support will be provided"
livevar=${esc_pn}_LIVE_COMMIT
EGIT_COMMIT=${!livevar-${EGIT_COMMIT}}
[[ ${!livevar} ]] \
&& ewarn "Using ${livevar}, no support will be provided"
livevar=${esc_pn}_LIVE_COMMIT_DATE
EGIT_COMMIT_DATE=${!livevar-${EGIT_COMMIT_DATE}}
[[ ${!livevar} ]] \
&& ewarn "Using ${livevar}, no support will be provided"
if [[ ${EGIT_COMMIT} && ${EGIT_COMMIT_DATE} ]]; then
die "EGIT_COMMIT and EGIT_COMMIT_DATE can not be specified simultaneously"
fi
}
# @FUNCTION: _git-r3_set_gitdir
# @USAGE: <repo-uri>
# @INTERNAL
# @DESCRIPTION:
# Obtain the local repository path and set it as GIT_DIR. Creates
# a new repository if necessary.
#
# <repo-uri> may be used to compose the path. It should therefore be
# a canonical URI to the repository.
_git-r3_set_gitdir() {
debug-print-function ${FUNCNAME} "$@"
local repo_name=${1#*://*/}
# strip the trailing slash
repo_name=${repo_name%/}
# strip common prefixes to make paths more likely to match
# e.g. git://X/Y.git vs https://X/git/Y.git
# (but just one of the prefixes)
case "${repo_name}" in
# gnome.org... who else?
browse/*) repo_name=${repo_name#browse/};;
# cgit can proxy requests to git
cgit/*) repo_name=${repo_name#cgit/};;
# pretty common
git/*) repo_name=${repo_name#git/};;
# gentoo.org
gitroot/*) repo_name=${repo_name#gitroot/};;
# sourceforge
p/*) repo_name=${repo_name#p/};;
# kernel.org
pub/scm/*) repo_name=${repo_name#pub/scm/};;
esac
# ensure a .git suffix, same reason
repo_name=${repo_name%.git}.git
# now replace all the slashes
repo_name=${repo_name//\//_}
local distdir=${PORTAGE_ACTUAL_DISTDIR:-${DISTDIR}}
: "${EGIT3_STORE_DIR:=${distdir}/git3-src}"
GIT_DIR=${EGIT3_STORE_DIR}/${repo_name}
EVCS_STORE_DIRS+=( "${GIT_DIR}" )
if [[ ! -d ${EGIT3_STORE_DIR} && ! ${EVCS_OFFLINE} ]]; then
(
addwrite /
mkdir -p "${EGIT3_STORE_DIR}"
) || die "Unable to create ${EGIT3_STORE_DIR}"
fi
addwrite "${EGIT3_STORE_DIR}"
if [[ ! -d ${GIT_DIR} ]]; then
if [[ ${EVCS_OFFLINE} ]]; then
eerror "A clone of the following repository is required to proceed:"
eerror " ${1}"
eerror "However, networking activity has been disabled using EVCS_OFFLINE and there"
eerror "is no local clone available."
die "No local clone of ${1}. Unable to proceed with EVCS_OFFLINE."
fi
local saved_umask
if [[ ${EVCS_UMASK} ]]; then
saved_umask=$(umask)
umask "${EVCS_UMASK}" || die "Bad options to umask: ${EVCS_UMASK}"
fi
mkdir "${GIT_DIR}" || die
git init --bare -b __init__ || die
if [[ ${saved_umask} ]]; then
umask "${saved_umask}" || die
fi
fi
}
# @FUNCTION: _git-r3_set_submodules
# @USAGE: <parent-path> <file-contents>
# @INTERNAL
# @DESCRIPTION:
# Parse .gitmodules contents passed as <file-contents>
# as in "$(cat .gitmodules)"). Composes a 'submodules' array that
# contains in order (name, URL, path) for each submodule.
#
# <parent-path> specifies path to current submodule (empty if top repo),
# and is used to support recursively specifying submodules. The path
# must include a trailing slash if it's not empty.
_git-r3_set_submodules() {
debug-print-function ${FUNCNAME} "$@"
local parent_path=${1}
local data=${2}
[[ -z ${parent_path} || ${parent_path} == */ ]] || die
# ( name url path ... )
submodules=()
local l
while read l; do
# submodule.<path>.path=<path>
# submodule.<path>.url=<url>
[[ ${l} == submodule.*.url=* ]] || continue
l=${l#submodule.}
local subname=${l%%.url=*}
local is_manually_specified=
# filter out on EGIT_SUBMODULES
if declare -p EGIT_SUBMODULES &>/dev/null; then
local p l_res res=
for p in "${EGIT_SUBMODULES[@]}"; do
if [[ ${p} == -* ]]; then
p=${p#-}
l_res=
else
l_res=1
fi
[[ ${parent_path}${subname} == ${p} ]] && res=${l_res}
done
if [[ ! ${res} ]]; then
einfo "Skipping submodule ${parent_path}${subname}"
continue
else
einfo "Using submodule ${parent_path}${subname}"
is_manually_specified=1
fi
fi
# skip modules that have 'update = none', bug #487262.
local upd=$(echo "${data}" | git config -f /dev/fd/0 \
submodule."${subname}".update)
[[ ${upd} == none && ! ${is_manually_specified} ]] && continue
# https://github.com/git/git/blob/master/refs.c#L31
# we are more restrictive than git itself but that should not
# cause any issues, #572312, #606950
# TODO: check escaped names for collisions
local enc_subname=${subname//[^a-zA-Z0-9-]/_}
submodules+=(
"${enc_subname}"
"$(echo "${data}" | git config -f /dev/fd/0 \
submodule."${subname}".url || die)"
"$(echo "${data}" | git config -f /dev/fd/0 \
submodule."${subname}".path || die)"
)
done < <(echo "${data}" | git config -f /dev/fd/0 -l || die)
}
# @FUNCTION: _git-r3_set_subrepos
# @USAGE: <submodule-uri> <parent-repo-uri>...
# @INTERNAL
# @DESCRIPTION:
# Create 'subrepos' array containing absolute (canonical) submodule URIs
# for the given <submodule-uri>. If the URI is relative, URIs will be
# constructed using all <parent-repo-uri>s. Otherwise, this single URI
# will be placed in the array.
_git-r3_set_subrepos() {
debug-print-function ${FUNCNAME} "$@"
local suburl=${1}
subrepos=( "${@:2}" )
if [[ ${suburl} == ./* || ${suburl} == ../* ]]; then
# drop all possible trailing slashes for consistency
subrepos=( "${subrepos[@]%%/}" )
while true; do
if [[ ${suburl} == ./* ]]; then
suburl=${suburl:2}
elif [[ ${suburl} == ../* ]]; then
suburl=${suburl:3}
# XXX: correctness checking
# drop the last path component
subrepos=( "${subrepos[@]%/*}" )
# and then the trailing slashes, again
subrepos=( "${subrepos[@]%%/}" )
else
break
fi
done
# append the preprocessed path to the preprocessed URIs
subrepos=( "${subrepos[@]/%//${suburl}}")
else
subrepos=( "${suburl}" )
fi
}
# @FUNCTION: _git-r3_is_local_repo
# @USAGE: <repo-uri>
# @INTERNAL
# @DESCRIPTION:
# Determine whether the given URI specifies a local (on-disk)
# repository.
_git-r3_is_local_repo() {
debug-print-function ${FUNCNAME} "$@"
local uri=${1}
[[ ${uri} == file://* || ${uri} == /* ]]
}
# @FUNCTION: git-r3_fetch
# @USAGE: [<repo-uri> [<remote-ref> [<local-id> [<commit-date>]]]]
# @DESCRIPTION:
# Fetch new commits to the local clone of repository.
#
# <repo-uri> specifies the repository URIs to fetch from, as a space-
# -separated list. The first URI will be used as repository group
# identifier and therefore must be used consistently. When not
# specified, defaults to ${EGIT_REPO_URI}.
#
# <remote-ref> specifies the remote ref or commit id to fetch.
# It is preferred to use 'refs/heads/<branch-name>' for branches
# and 'refs/tags/<tag-name>' for tags. Other options are 'HEAD'
# for upstream default branch and hexadecimal commit SHA1. Defaults
# to the first of EGIT_COMMIT, EGIT_BRANCH or literal 'HEAD' that
# is set to a non-null value.
#
# <local-id> specifies the local branch identifier that will be used to
# locally store the fetch result. It should be unique to multiple
# fetches within the repository that can be performed at the same time
# (including parallel merges). It defaults to ${CATEGORY}/${PN}/${SLOT%/*}.
# This default should be fine unless you are fetching multiple trees
# from the same repository in the same ebuild.
#
# <commit-date> requests attempting to use repository state as of specific
# date. For more details, see EGIT_COMMIT_DATE.
#
# The fetch operation will affect the EGIT_STORE only. It will not touch
# the working copy, nor export any environment variables.
# If the repository contains submodules, they will be fetched
# recursively.
git-r3_fetch() {
debug-print-function ${FUNCNAME} "$@"
# disable password prompts, https://bugs.gentoo.org/701276
local -x GIT_TERMINAL_PROMPT=0
# process repos first since we create repo_name from it
local repos
if [[ ${1} ]]; then
repos=( ${1} )
elif [[ $(declare -p EGIT_REPO_URI) == "declare -a"* ]]; then
repos=( "${EGIT_REPO_URI[@]}" )
else
repos=( ${EGIT_REPO_URI} )
fi
[[ ${repos[@]} ]] || die "No URI provided and EGIT_REPO_URI unset"
local r
for r in "${repos[@]}"; do
if [[ ${r} == git:* || ${r} == http:* ]]; then
ewarn "git-r3: ${r%%:*} protocol is completely insecure and may render the ebuild"
ewarn "easily susceptible to MITM attacks (even if used only as fallback). Please"
ewarn "use https instead."
ewarn "[URI: ${r}]"
fi
done
local -x GIT_DIR
_git-r3_set_gitdir "${repos[0]}"
einfo "Repository id: ${GIT_DIR##*/}"
# prepend the local mirror if applicable
if [[ ${EGIT_MIRROR_URI} ]]; then
repos=(
"${EGIT_MIRROR_URI%/}/${GIT_DIR##*/}"
"${repos[@]}"
)
fi
# get the default values for the common variables and override them
local branch_name=${EGIT_BRANCH}
local commit_id=${2:-${EGIT_COMMIT}}
local commit_date=${4:-${EGIT_COMMIT_DATE}}
# get the name and do some more processing:
# 1) kill .git suffix,
# 2) underscore (remaining) non-variable characters,
# 3) add preceding underscore if it starts with a digit,
# 4) uppercase.
local override_name=${GIT_DIR##*/}
override_name=${override_name%.git}
override_name=${override_name//[^a-zA-Z0-9_]/_}
override_name=${override_name^^}
local varmap=(
REPO:repos
BRANCH:branch_name
COMMIT:commit_id
COMMIT_DATE:commit_date
)
local localvar livevar live_warn= override_vars=()
for localvar in "${varmap[@]}"; do
livevar=EGIT_OVERRIDE_${localvar%:*}_${override_name}
localvar=${localvar#*:}
override_vars+=( "${livevar}" )
if [[ -n ${!livevar} ]]; then
[[ ${localvar} == repos ]] && repos=()
live_warn=1
ewarn "Using ${livevar}=${!livevar}"
declare "${localvar}=${!livevar}"
fi
done
if [[ ${live_warn} ]]; then
ewarn "No support will be provided."
else
einfo "To override fetched repository properties, use:"
local x
for x in "${override_vars[@]}"; do
einfo " ${x}"
done
einfo
fi
# set final variables after applying overrides
local branch=${branch_name:+refs/heads/${branch_name}}
local remote_ref=${commit_id:-${branch:-HEAD}}
local local_id=${3:-${CATEGORY}/${PN}/${SLOT%/*}}
local local_ref=refs/git-r3/${local_id}/__main__
# try to fetch from the remote
local success saved_umask
if [[ ${EVCS_UMASK} ]]; then
saved_umask=$(umask)
umask "${EVCS_UMASK}" || die "Bad options to umask: ${EVCS_UMASK}"
fi
for r in "${repos[@]}"; do
if [[ ! ${EVCS_OFFLINE} ]]; then
einfo "Fetching ${r} ..."
local fetch_command=( git fetch "${r}" )
local clone_type=${EGIT_CLONE_TYPE}
if [[ ${clone_type} == mirror ]]; then
fetch_command+=(
--prune
# mirror the remote branches as local branches
"+refs/heads/*:refs/heads/*"
# pull tags explicitly in order to prune them properly
"+refs/tags/*:refs/tags/*"
# notes in case something needs them
"+refs/notes/*:refs/notes/*"
# pullrequest refs are useful for testing incoming changes
"+refs/pull/*/head:refs/pull/*"
# and HEAD in case we need the default branch
# (we keep it in refs/git-r3 since otherwise --prune interferes)
"+HEAD:refs/git-r3/HEAD"
# fetch the specifc commit_ref to deal with orphan commits
"${remote_ref}"
)
else # single or shallow
local fetch_l fetch_r
if [[ ${remote_ref} == HEAD ]]; then
# HEAD
fetch_l=HEAD
elif [[ ${remote_ref} == refs/* ]]; then
# regular branch, tag or some other explicit ref
fetch_l=${remote_ref}
else
# tag or commit id...
# let ls-remote figure it out
local tagref=$(git ls-remote "${r}" "refs/tags/${remote_ref}")
# if it was a tag, ls-remote obtained a hash
if [[ ${tagref} ]]; then
# tag
fetch_l=refs/tags/${remote_ref}
else
# commit id
# so we need to fetch the whole branch
if [[ ${branch} ]]; then
fetch_l=${branch}
else
fetch_l=HEAD
fi
# fetching by commit in shallow mode? can't do.
if [[ ${clone_type} == shallow ]]; then
clone_type=single
fi
fi
fi
# checkout by date does not make sense in shallow mode
if [[ ${commit_date} && ${clone_type} == shallow ]]; then
clone_type=single
fi
if [[ ${fetch_l} == HEAD ]]; then
fetch_r=refs/git-r3/HEAD
else
fetch_r=${fetch_l}
fi
fetch_command+=(
"+${fetch_l}:${fetch_r}"
)
if [[ ${clone_type} == single+tags ]]; then
fetch_command+=(
# pull tags explicitly as requested
"+refs/tags/*:refs/tags/*"
)
fi
fi
if [[ ${clone_type} == shallow ]]; then
if _git-r3_is_local_repo; then
# '--depth 1' causes sandbox violations with local repos
# bug #491260
clone_type=single
elif [[ ! $(git rev-parse --quiet --verify "${fetch_r}") ]]
then
# use '--depth 1' when fetching a new branch
fetch_command+=( --depth 1 )
fi
else # non-shallow mode
if [[ -f ${GIT_DIR}/shallow ]]; then
fetch_command+=( --unshallow )
fi
fi
set -- "${fetch_command[@]}"
echo "${@}" >&2
"${@}" || continue
if [[ ${clone_type} == mirror || ${fetch_l} == HEAD ]]; then
# update our HEAD to match our remote HEAD ref
git symbolic-ref HEAD refs/git-r3/HEAD \
|| die "Unable to update HEAD"
fi
fi
# now let's see what the user wants from us
if [[ ${commit_date} ]]; then
local dated_commit_id=$(
git rev-list --first-parent --before="${commit_date}" \
-n 1 "${remote_ref}"
)
if [[ ${?} -ne 0 ]]; then
die "Listing ${remote_ref} failed (wrong ref?)."
elif [[ ! ${dated_commit_id} ]]; then
die "Unable to find commit for date ${commit_date}."
else
set -- git update-ref --no-deref "${local_ref}" "${dated_commit_id}"
fi
else
local full_remote_ref=$(
git rev-parse --verify --symbolic-full-name "${remote_ref}"
)
if [[ ${full_remote_ref} ]]; then
# when we are given a ref, create a symbolic ref
# so that we preserve the actual argument
set -- git symbolic-ref "${local_ref}" "${full_remote_ref}"
else
# otherwise, we were likely given a commit id
set -- git update-ref --no-deref "${local_ref}" "${remote_ref}"
fi
fi
echo "${@}" >&2
if ! "${@}"; then
if [[ ${EVCS_OFFLINE} ]]; then
eerror "A clone of the following repository is required to proceed:"
eerror " ${r}"
eerror "However, networking activity has been disabled using EVCS_OFFLINE and the local"
eerror "clone does not have requested ref:"
eerror " ${remote_ref}"
die "Local clone of ${r} does not have requested ref: ${remote_ref}. Unable to proceed with EVCS_OFFLINE."
else
die "Referencing ${remote_ref} failed (wrong ref?)."
fi
fi
if [[ ${EGIT_LFS} ]]; then
# Fetch the LFS files from the current ref (if any)
local lfs_fetch_command=( git lfs fetch "${r}" "${remote_ref}" )
case "${EGIT_LFS_CLONE_TYPE}" in
shallow)
if [[ -d ${GIT_DIR}/lfs/objects ]] && ! rmdir "${GIT_DIR}"/lfs/objects 2> /dev/null; then
# Only prune if the lfs directory is not empty.
# The prune command can take a very long time to resolve even if there are no lfs objects.
lfs_fetch_command+=(
--prune
)
fi
;;
single)
;;
mirror)
lfs_fetch_command+=(
--all
)
;;
*)
die "Invalid EGIT_LFS_CLONE_TYPE=${EGIT_LFS_CLONE_TYPE}"
esac
set -- "${lfs_fetch_command[@]}"
echo "${@}" >&2
"${@}" || die
elif [[ -d ${GIT_DIR}/lfs && ${EGIT_LFS_CLONE_TYPE} == shallow ]]; then
# Cleanup the LFS files from old checkouts if LFS support has been turned off.
rm -fr ${GIT_DIR}/lfs || die
fi
success=1
break
done
if [[ ${saved_umask} ]]; then
umask "${saved_umask}" || die
fi
[[ ${success} ]] || die "Unable to fetch from any of EGIT_REPO_URI"
# submodules can reference commits in any branch
# always use the 'mirror' mode to accommodate that, bug #503332
local EGIT_CLONE_TYPE=mirror
# recursively fetch submodules
if git cat-file -e "${local_ref}":.gitmodules &>/dev/null; then
local submodules
_git-r3_set_submodules "${_GIT_SUBMODULE_PATH}" \
"$(git cat-file -p "${local_ref}":.gitmodules || die)"
while [[ ${submodules[@]} ]]; do
local subname=${submodules[0]}
local url=${submodules[1]}
local path=${submodules[2]}
# use only submodules for which path does exist
# (this is in par with 'git submodule'), bug #551100
# note: git cat-file does not work for submodules
if [[ $(git ls-tree -d "${local_ref}" "${path}") ]]
then
local commit=$(git rev-parse "${local_ref}:${path}" || die)
if [[ ! ${commit} ]]; then
die "Unable to get commit id for submodule ${subname}"
fi
local subrepos
_git-r3_set_subrepos "${url}" "${repos[@]}"
_GIT_SUBMODULE_PATH=${_GIT_SUBMODULE_PATH}${path}/ \
git-r3_fetch "${subrepos[*]}" "${commit}" \
"${local_id}/${subname}" ""
fi
submodules=( "${submodules[@]:3}" ) # shift
done
fi
}
# @FUNCTION: git-r3_checkout
# @USAGE: [<repo-uri> [<checkout-path> [<local-id> [<checkout-paths>...]]]]
# @DESCRIPTION:
# Check the previously fetched tree to the working copy.
#
# <repo-uri> specifies the repository URIs, as a space-separated list.
# The first URI will be used as repository group identifier
# and therefore must be used consistently with git-r3_fetch.
# The remaining URIs are not used and therefore may be omitted.
# When not specified, defaults to ${EGIT_REPO_URI}.
#
# <checkout-path> specifies the path to place the checkout. It defaults
# to ${EGIT_CHECKOUT_DIR} if set, otherwise to ${WORKDIR}/${P}.
#
# <local-id> needs to specify the local identifier that was used
# for respective git-r3_fetch.
#
# If <checkout-paths> are specified, then the specified paths are passed
# to 'git checkout' to effect a partial checkout. Please note that such
# checkout will not cause the repository to switch branches,
# and submodules will be skipped at the moment. The submodules matching
# those paths might be checked out in a future version of the eclass.
#
# The checkout operation will write to the working copy, and export
# the repository state into the environment. If the repository contains
# submodules, they will be checked out recursively.
git-r3_checkout() {
debug-print-function ${FUNCNAME} "$@"
local repos
if [[ ${1} ]]; then
repos=( ${1} )
elif [[ $(declare -p EGIT_REPO_URI) == "declare -a"* ]]; then
repos=( "${EGIT_REPO_URI[@]}" )
else
repos=( ${EGIT_REPO_URI} )
fi
local out_dir=${2:-${EGIT_CHECKOUT_DIR:-${WORKDIR}/${P}}}
local local_id=${3:-${CATEGORY}/${PN}/${SLOT%/*}}
local checkout_paths=( "${@:4}" )
local -x GIT_DIR
_git-r3_set_gitdir "${repos[0]}"
einfo "Checking out ${repos[0]} to ${out_dir} ..."
if ! git cat-file -e refs/git-r3/"${local_id}"/__main__; then
die "Logic error: no local clone of ${repos[0]}. git-r3_fetch not used?"
fi
local remote_ref=$(
git symbolic-ref --quiet refs/git-r3/"${local_id}"/__main__
)
local new_commit_id=$(
git rev-parse --verify refs/git-r3/"${local_id}"/__main__
)
git-r3_sub_checkout() {
local orig_repo=${GIT_DIR}
local -x GIT_DIR=${out_dir}/.git
local -x GIT_WORK_TREE=${out_dir}
mkdir -p "${out_dir}" || die
# use git init+fetch instead of clone since the latter doesn't like
# non-empty directories.
git init --quiet -b __init__ || die
if [[ ${EGIT_LFS} ]]; then
# The "skip-repo" flag will just skip the installation of the pre-push hooks.
# We don't use these hook as we don't do any pushes
git lfs install --local --skip-repo || die
fi
# setup 'alternates' to avoid copying objects
echo "${orig_repo}/objects" > "${GIT_DIR}"/objects/info/alternates || die
# now copy the refs
cp -R "${orig_repo}"/refs/* "${GIT_DIR}"/refs/ || die
if [[ -f ${orig_repo}/packed-refs ]]; then
cp "${orig_repo}"/packed-refs "${GIT_DIR}"/packed-refs || die
fi
# mark this directory as "safe" so that src_install() can access it
# https://bugs.gentoo.org/879353
git config --global --add safe.directory \
"$(cd "${out_dir}" && echo "${PWD}")" || die
# (no need to copy HEAD, we will set it via checkout)
if [[ -f ${orig_repo}/shallow ]]; then
cp "${orig_repo}"/shallow "${GIT_DIR}"/ || die
fi
set -- git checkout --quiet
if [[ ${remote_ref} ]]; then
set -- "${@}" "${remote_ref#refs/heads/}"
else
set -- "${@}" "${new_commit_id}"
fi
if [[ ${checkout_paths[@]} ]]; then
set -- "${@}" -- "${checkout_paths[@]}"
fi
echo "${@}" >&2
"${@}" || die "git checkout ${remote_ref:-${new_commit_id}} failed"
# If any filters in any of the ".gitattributes" files specifies lfs,
# then this repo is most likely storing files with git lfs.
local has_git_lfs_filters=$(
git grep "filter=lfs" -- ".gitattributes" "**/.gitattributes"
)
if [[ $has_git_lfs_filters ]]; then
# This is used for issuing QA warnings regarding LFS files in the repo (or lack thereof)
_EGIT_LFS_FILTERS_FOUND="yes"
fi
}
git-r3_sub_checkout
unset -f git-r3_sub_checkout
local old_commit_id=$(
git rev-parse --quiet --verify refs/git-r3/"${local_id}"/__old__
)
if [[ ! ${old_commit_id} ]]; then
echo "GIT NEW branch -->"
echo " repository: ${repos[0]}"
echo " at the commit: ${new_commit_id}"
else
# diff against previous revision
echo "GIT update -->"
echo " repository: ${repos[0]}"
# write out message based on the revisions
if [[ "${old_commit_id}" != "${new_commit_id}" ]]; then
echo " updating from commit: ${old_commit_id}"
echo " to commit: ${new_commit_id}"
set -- git --no-pager diff --stat \
${old_commit_id}..${new_commit_id}
if [[ ${checkout_paths[@]} ]]; then
set -- "${@}" -- "${checkout_paths[@]}"
fi
"${@}"
else
echo " at the commit: ${new_commit_id}"
fi
fi
git update-ref --no-deref refs/git-r3/"${local_id}"/{__old__,__main__} || die
# recursively checkout submodules
if [[ -f ${out_dir}/.gitmodules && ! ${checkout_paths} ]]; then
local submodules
_git-r3_set_submodules "${_GIT_SUBMODULE_PATH}" \
"$(<"${out_dir}"/.gitmodules)"
while [[ ${submodules[@]} ]]; do
local subname=${submodules[0]}
local url=${submodules[1]}
local path=${submodules[2]}
# use only submodules for which path does exist
# (this is in par with 'git submodule'), bug #551100
if [[ -d ${out_dir}/${path} ]]; then
local subrepos
_git-r3_set_subrepos "${url}" "${repos[@]}"
_GIT_SUBMODULE_PATH=${_GIT_SUBMODULE_PATH}${path}/ \
git-r3_checkout "${subrepos[*]}" "${out_dir}/${path}" \
"${local_id}/${subname}"
fi
submodules=( "${submodules[@]:3}" ) # shift
done
fi
# keep this *after* submodules
export EGIT_DIR=${GIT_DIR}
export EGIT_VERSION=${new_commit_id}
}
# @FUNCTION: git-r3_peek_remote_ref
# @USAGE: [<repo-uri> [<remote-ref>]]
# @DESCRIPTION:
# Peek the reference in the remote repository and print the matching
# (newest) commit SHA1.
#
# <repo-uri> specifies the repository URIs to fetch from, as a space-
# -separated list. When not specified, defaults to ${EGIT_REPO_URI}.
#
# <remote-ref> specifies the remote ref to peek. It is preferred to use
# 'refs/heads/<branch-name>' for branches and 'refs/tags/<tag-name>'
# for tags. Alternatively, 'HEAD' may be used for upstream default
# branch. Defaults to the first of EGIT_COMMIT, EGIT_BRANCH or literal
# 'HEAD' that is set to a non-null value.
#
# The operation will be done purely on the remote, without using local
# storage. If commit SHA1 is provided as <remote-ref>, the function will
# fail due to limitations of git protocol.
#
# On success, the function returns 0 and writes hexadecimal commit SHA1
# to stdout. On failure, the function returns 1.
git-r3_peek_remote_ref() {
debug-print-function ${FUNCNAME} "$@"
local repos
if [[ ${1} ]]; then
repos=( ${1} )
elif [[ $(declare -p EGIT_REPO_URI) == "declare -a"* ]]; then
repos=( "${EGIT_REPO_URI[@]}" )
else
repos=( ${EGIT_REPO_URI} )
fi
local branch=${EGIT_BRANCH:+refs/heads/${EGIT_BRANCH}}
local remote_ref=${2:-${EGIT_COMMIT:-${branch:-HEAD}}}
[[ ${repos[@]} ]] || die "No URI provided and EGIT_REPO_URI unset"
local r success
for r in "${repos[@]}"; do
einfo "Peeking ${remote_ref} on ${r} ..." >&2
local lookup_ref
if [[ ${remote_ref} == refs/* || ${remote_ref} == HEAD ]]
then
lookup_ref=${remote_ref}
else
# ls-remote by commit is going to fail anyway,
# so we may as well pass refs/tags/ABCDEF...
lookup_ref=refs/tags/${remote_ref}
fi
# split on whitespace
local ref=(
$(git ls-remote "${r}" "${lookup_ref}")
)
if [[ ${ref[0]} ]]; then
echo "${ref[0]}"
return 0
fi
done
return 1
}
git-r3_src_fetch() {
debug-print-function ${FUNCNAME} "$@"
if [[ ! ${EGIT3_STORE_DIR} && ${EGIT_STORE_DIR} ]]; then
ewarn "You have set EGIT_STORE_DIR but not EGIT3_STORE_DIR. Please consider"
ewarn "setting EGIT3_STORE_DIR for git-r3.eclass. It is recommended to use"
ewarn "a different directory than EGIT_STORE_DIR to ease removing old clones"
ewarn "when git-2 eclass becomes deprecated."
fi
_git-r3_env_setup
git-r3_fetch
}
git-r3_src_unpack() {
debug-print-function ${FUNCNAME} "$@"
_git-r3_env_setup
git-r3_src_fetch
git-r3_checkout
if [[ ! ${EGIT_LFS} && ${_EGIT_LFS_FILTERS_FOUND} ]]; then
eqawarn "QA Notice: There are Git LFS filters setup in the cloned repo, consider using EGIT_LFS!"
fi
if [[ ${EGIT_LFS} && ! ${_EGIT_LFS_FILTERS_FOUND} ]]; then
eqawarn "QA Notice: There are no Git LFS filters setup in the cloned repo. EGIT_LFS will do nothing!"
fi
}
# https://bugs.gentoo.org/show_bug.cgi?id=482666
git-r3_pkg_needrebuild() {
debug-print-function ${FUNCNAME} "$@"
local new_commit_id=$(git-r3_peek_remote_ref)
[[ ${new_commit_id} && ${EGIT_VERSION} ]] || die "Lookup failed"
if [[ ${EGIT_VERSION} != ${new_commit_id} ]]; then
einfo "Update from ${EGIT_VERSION} to ${new_commit_id}"
else
einfo "Local and remote at ${EGIT_VERSION}"
fi
[[ ${EGIT_VERSION} != ${new_commit_id} ]]
}
# 'export' locally until this gets into EAPI
pkg_needrebuild() { git-r3_pkg_needrebuild; }
fi
EXPORT_FUNCTIONS src_unpack