# Copyright 1999-2024 Gentoo Authors # Distributed under the terms of the GNU General Public License v2 # @ECLASS: git-r3.eclass # @MAINTAINER: # Michał Górny # @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: # @INTERNAL # @DESCRIPTION: # Obtain the local repository path and set it as GIT_DIR. Creates # a new repository if necessary. # # 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: # @INTERNAL # @DESCRIPTION: # Parse .gitmodules contents passed as # as in "$(cat .gitmodules)"). Composes a 'submodules' array that # contains in order (name, URL, path) for each submodule. # # 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= # submodule..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: ... # @INTERNAL # @DESCRIPTION: # Create 'subrepos' array containing absolute (canonical) submodule URIs # for the given . If the URI is relative, URIs will be # constructed using all 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: # @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: [ [ [ []]]] # @DESCRIPTION: # Fetch new commits to the local clone of repository. # # 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}. # # specifies the remote ref or commit id to fetch. # It is preferred to use 'refs/heads/' for branches # and 'refs/tags/' 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. # # 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. # # 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: [ [ [ [...]]]] # @DESCRIPTION: # Check the previously fetched tree to the working copy. # # 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}. # # specifies the path to place the checkout. It defaults # to ${EGIT_CHECKOUT_DIR} if set, otherwise to ${WORKDIR}/${P}. # # needs to specify the local identifier that was used # for respective git-r3_fetch. # # If 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: [ []] # @DESCRIPTION: # Peek the reference in the remote repository and print the matching # (newest) commit SHA1. # # specifies the repository URIs to fetch from, as a space- # -separated list. When not specified, defaults to ${EGIT_REPO_URI}. # # specifies the remote ref to peek. It is preferred to use # 'refs/heads/' for branches and 'refs/tags/' # 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 , 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