# Copyright 1999-2011 Gentoo Foundation # Distributed under the terms of the GNU General Public License v2 # $Header: /var/cvsroot/gentoo-x86/eclass/git-2.eclass,v 1.14 2011/08/22 04:46:31 vapier Exp $ # @ECLASS: git-2.eclass # @MAINTAINER: # Donnie Berkholz # @BLURB: Eclass for fetching and unpacking git repositories. # @DESCRIPTION: # Eclass for easing maitenance of live ebuilds using git as remote repository. # Eclass support working with git submodules and branching. # This eclass support all EAPIs EXPORT_FUNCTIONS src_unpack DEPEND="dev-vcs/git" # @ECLASS-VARIABLE: EGIT_SOURCEDIR # @DESCRIPTION: # This variable specifies destination where the cloned # data are copied to. # # EGIT_SOURCEDIR="${S}" # @ECLASS-VARIABLE: EGIT_STORE_DIR # @DESCRIPTION: # Storage directory for git sources. # # EGIT_STORE_DIR="${DISTDIR}/egit-src" # @ECLASS-VARIABLE: EGIT_HAS_SUBMODULES # @DEFAULT_UNSET # @DESCRIPTION: # If non-empty this variable enables support for git submodules in our # checkout. Also this makes the checkout to be non-bare for now. # @ECLASS-VARIABLE: EGIT_OPTIONS # @DEFAULT_UNSET # @DESCRIPTION: # Variable specifying additional options for fetch command. # @ECLASS-VARIABLE: EGIT_MASTER # @DESCRIPTION: # Variable for specifying master branch. # Usefull when upstream don't have master branch or name it differently. # # EGIT_MASTER="master" # @ECLASS-VARIABLE: EGIT_PROJECT # @DESCRIPTION: # Variable specifying name for the folder where we check out the git # repository. Value of this variable should be unique in the # EGIT_STORE_DIR as otherwise you would override another repository. # # EGIT_PROJECT="${EGIT_REPO_URI##*/}" # @ECLASS-VARIABLE: EGIT_DIR # @DESCRIPTION: # Directory where we want to store the git data. # This variable should not be overriden. # # EGIT_DIR="${EGIT_STORE_DIR}/${EGIT_PROJECT}" # @ECLASS-VARIABLE: EGIT_REPO_URI # @REQUIRED # @DEFAULT_UNSET # @DESCRIPTION: # URI for the repository # e.g. http://foo, git://bar # # Support multiple values: # EGIT_REPO_URI="git://a/b.git http://c/d.git" # @ECLASS-VARIABLE: EVCS_OFFLINE # @DEFAULT_UNSET # @DESCRIPTION: # If non-empty this variable prevents performance of any online # operations. # @ECLASS-VARIABLE: EGIT_BRANCH # @DESCRIPTION: # Variable containing branch name we want to check out. # It can be overriden via env using packagename_LIVE_BRANCH # variable. # # EGIT_BRANCH="${EGIT_MASTER}" # @ECLASS-VARIABLE: EGIT_COMMIT # @DESCRIPTION: # Variable containing commit hash/tag we want to check out. # It can be overriden via env using packagename_LIVE_COMMIT # variable. # # EGIT_COMMIT="${EGIT_BRANCH}" # @ECLASS-VARIABLE: EGIT_REPACK # @DEFAULT_UNSET # @DESCRIPTION: # If non-empty this variable specifies that repository will be repacked to # save space. However this can take a REALLY LONG time with VERY big # repositories. # @ECLASS-VARIABLE: EGIT_PRUNE # @DEFAULT_UNSET # @DESCRIPTION: # If non-empty this variable enables pruning all loose objects on each fetch. # This is useful if upstream rewinds and rebases branches often. # @ECLASS-VARIABLE: EGIT_NONBARE # @DEFAULT_UNSET # @DESCRIPTION: # If non-empty this variable specifies that all checkouts will be done using # non bare repositories. This is useful if you can't operate with bare # checkouts for some reason. # @ECLASS-VARIABLE: EGIT_NOUNPACK # @DEFAULT_UNSET # @DESCRIPTION: # If non-empty this variable bans unpacking of ${A} content into the srcdir. # Default behaviour is to unpack ${A} content. # @FUNCTION: git-2_init_variables # @DESCRIPTION: # Internal function initializing all git variables. # We define it in function scope so user can define # all the variables before and after inherit. git-2_init_variables() { debug-print-function ${FUNCNAME} "$@" local x : ${EGIT_SOURCEDIR="${S}"} : ${EGIT_STORE_DIR:="${PORTAGE_ACTUAL_DISTDIR-${DISTDIR}}/egit-src"} : ${EGIT_HAS_SUBMODULES:=} : ${EGIT_OPTIONS:=} : ${EGIT_MASTER:=master} eval x="\$${PN//[-+]/_}_LIVE_REPO" EGIT_REPO_URI=${x:-${EGIT_REPO_URI}} [[ -z ${EGIT_REPO_URI} ]] && die "EGIT_REPO_URI must have some value" : ${EVCS_OFFLINE:=} eval x="\$${PN//[-+]/_}_LIVE_BRANCH" [[ -n ${x} ]] && ewarn "QA: using \"${PN//[-+]/_}_LIVE_BRANCH\" variable, you won't get any support" EGIT_BRANCH=${x:-${EGIT_BRANCH:-${EGIT_MASTER}}} eval x="\$${PN//[-+]/_}_LIVE_COMMIT" [[ -n ${x} ]] && ewarn "QA: using \"${PN//[-+]/_}_LIVE_COMMIT\" variable, you won't get any support" EGIT_COMMIT=${x:-${EGIT_COMMIT:-${EGIT_BRANCH}}} : ${EGIT_REPACK:=} : ${EGIT_PRUNE:=} } # @FUNCTION: git-2_submodules # @DESCRIPTION: # Internal function wrapping the submodule initialisation and update. git-2_submodules() { debug-print-function ${FUNCNAME} "$@" if [[ -n ${EGIT_HAS_SUBMODULES} ]]; then if [[ -n ${EVCS_OFFLINE} ]]; then # for submodules operations we need to be online debug-print "${FUNCNAME}: not updating submodules in offline mode" return 1 fi debug-print "${FUNCNAME}: working in \"${1}\"" pushd "${EGIT_DIR}" > /dev/null debug-print "${FUNCNAME}: git submodule init" git submodule init || die debug-print "${FUNCNAME}: git submodule sync" git submodule sync || die debug-print "${FUNCNAME}: git submodule update" git submodule update || die popd > /dev/null fi } # @FUNCTION: git-2_branch # @DESCRIPTION: # Internal function that changes branch for the repo based on EGIT_COMMIT and # EGIT_BRANCH variables. git-2_branch() { debug-print-function ${FUNCNAME} "$@" local branchname src debug-print "${FUNCNAME}: working in \"${EGIT_SOURCEDIR}\"" pushd "${EGIT_SOURCEDIR}" > /dev/null local branchname=branch-${EGIT_BRANCH} src=origin/${EGIT_BRANCH} if [[ ${EGIT_COMMIT} != ${EGIT_BRANCH} ]]; then branchname=tree-${EGIT_COMMIT} src=${EGIT_COMMIT} fi debug-print "${FUNCNAME}: git checkout -b ${branchname} ${src}" git checkout -b ${branchname} ${src} \ || die "${FUNCNAME}: changing the branch failed" popd > /dev/null } # @FUNCTION: git-2_gc # @DESCRIPTION: # Internal function running garbage collector on checked out tree. git-2_gc() { debug-print-function ${FUNCNAME} "$@" local args pushd "${EGIT_DIR}" > /dev/null if [[ -n ${EGIT_REPACK} || -n ${EGIT_PRUNE} ]]; then ebegin "Garbage collecting the repository" [[ -n ${EGIT_PRUNE} ]] && args='--prune' debug-print "${FUNCNAME}: git gc ${args}" git gc ${args} eend $? fi popd > /dev/null } # @FUNCTION: git-2_prepare_storedir # @DESCRIPTION: # Internal function preparing directory where we are going to store SCM # repository. git-2_prepare_storedir() { debug-print-function ${FUNCNAME} "$@" local clone_dir # initial clone, we have to create master git storage directory and play # nicely with sandbox if [[ ! -d ${EGIT_STORE_DIR} ]]; then debug-print "${FUNCNAME}: Creating git main storage directory" addwrite / mkdir -p "${EGIT_STORE_DIR}" \ || die "${FUNCNAME}: can't mkdir \"${EGIT_STORE_DIR}\"" fi # allow writing into EGIT_STORE_DIR addwrite "${EGIT_STORE_DIR}" # calculate the proper store dir for data # If user didn't specify the EGIT_DIR, we check if he did specify # the EGIT_PROJECT or get the folder name from EGIT_REPO_URI. [[ -z ${EGIT_REPO_URI##*/} ]] && EGIT_REPO_URI="${EGIT_REPO_URI%/}" if [[ -z ${EGIT_DIR} ]]; then if [[ -n ${EGIT_PROJECT} ]]; then clone_dir=${EGIT_PROJECT} else clone_dir=${EGIT_REPO_URI##*/} fi EGIT_DIR=${EGIT_STORE_DIR}/${clone_dir} fi export EGIT_DIR=${EGIT_DIR} debug-print "${FUNCNAME}: Storing the repo into \"${EGIT_DIR}\"." } # @FUNCTION: git-2_move_source # @DESCRIPTION: # Internal function moving sources from the EGIT_DIR to EGIT_SOURCEDIR dir. git-2_move_source() { debug-print-function ${FUNCNAME} "$@" debug-print "${FUNCNAME}: ${MOVE_COMMAND} \"${EGIT_DIR}\" \"${EGIT_SOURCEDIR}\"" pushd "${EGIT_DIR}" > /dev/null mkdir -p "${EGIT_SOURCEDIR}" \ || die "${FUNCNAME}: failed to create ${EGIT_SOURCEDIR}" ${MOVE_COMMAND} "${EGIT_SOURCEDIR}" \ || die "${FUNCNAME}: sync to \"${EGIT_SOURCEDIR}\" failed" popd > /dev/null } # @FUNCTION: git-2_initial_clone # @DESCRIPTION: # Internal function running initial clone on specified repo_uri. git-2_initial_clone() { debug-print-function ${FUNCNAME} "$@" local repo_uri EGIT_REPO_URI_SELECTED="" for repo_uri in ${EGIT_REPO_URI}; do debug-print "${FUNCNAME}: git clone ${EGIT_LOCAL_OPTIONS} \"${repo_uri}\" \"${EGIT_DIR}\"" git clone ${EGIT_LOCAL_OPTIONS} "${repo_uri}" "${EGIT_DIR}" if [[ $? -eq 0 ]]; then # global variable containing the repo_name we will be using debug-print "${FUNCNAME}: EGIT_REPO_URI_SELECTED=\"${repo_uri}\"" EGIT_REPO_URI_SELECTED="${repo_uri}" break fi done if [[ -z ${EGIT_REPO_URI_SELECTED} ]]; then die "${FUNCNAME}: can't fetch from ${EGIT_REPO_URI}" fi } # @FUNCTION: git-2_update_repo # @DESCRIPTION: # Internal function running update command on specified repo_uri. git-2_update_repo() { debug-print-function ${FUNCNAME} "$@" local repo_uri if [[ -n ${EGIT_LOCAL_NONBARE} ]]; then # checkout master branch and drop all other local branches git checkout ${EGIT_MASTER} || die "${FUNCNAME}: can't checkout master branch ${EGIT_MASTER}" for x in $(git branch | grep -v "* ${EGIT_MASTER}" | tr '\n' ' '); do debug-print "${FUNCNAME}: git branch -D ${x}" git branch -D ${x} > /dev/null done fi EGIT_REPO_URI_SELECTED="" for repo_uri in ${EGIT_REPO_URI}; do # git urls might change, so reset it git config remote.origin.url "${repo_uri}" debug-print "${EGIT_UPDATE_CMD}" ${EGIT_UPDATE_CMD} > /dev/null if [[ $? -eq 0 ]]; then # global variable containing the repo_name we will be using debug-print "${FUNCNAME}: EGIT_REPO_URI_SELECTED=\"${repo_uri}\"" EGIT_REPO_URI_SELECTED="${repo_uri}" break fi done if [[ -z ${EGIT_REPO_URI_SELECTED} ]]; then die "${FUNCNAME}: can't update from ${EGIT_REPO_URI}" fi } # @FUNCTION: git-2_fetch # @DESCRIPTION: # Internal function fetching repository from EGIT_REPO_URI and storing it in # specified EGIT_STORE_DIR. git-2_fetch() { debug-print-function ${FUNCNAME} "$@" local oldsha cursha repo_type [[ -n ${EGIT_LOCAL_NONBARE} ]] && repo_type="non-bare repository" || repo_type="bare repository" if [[ ! -d ${EGIT_DIR} ]]; then git-2_initial_clone pushd "${EGIT_DIR}" > /dev/null cursha=$(git rev-parse ${UPSTREAM_BRANCH}) echo "GIT NEW clone -->" echo " repository: ${EGIT_REPO_URI_SELECTED}" echo " at the commit: ${cursha}" popd > /dev/null elif [[ -n ${EVCS_OFFLINE} ]]; then pushd "${EGIT_DIR}" > /dev/null cursha=$(git rev-parse ${UPSTREAM_BRANCH}) echo "GIT offline update -->" echo " repository: $(git config remote.origin.url)" echo " at the commit: ${cursha}" popd > /dev/null else pushd "${EGIT_DIR}" > /dev/null oldsha=$(git rev-parse ${UPSTREAM_BRANCH}) git-2_update_repo cursha=$(git rev-parse ${UPSTREAM_BRANCH}) # fetch updates echo "GIT update -->" echo " repository: ${EGIT_REPO_URI_SELECTED}" # write out message based on the revisions if [[ "${oldsha}" != "${cursha}" ]]; then echo " updating from commit: ${oldsha}" echo " to commit: ${cursha}" else echo " at the commit: ${cursha}" fi # print nice statistic of what was changed git --no-pager diff --stat ${oldsha}..${UPSTREAM_BRANCH} popd > /dev/null fi # export the version the repository is at export EGIT_VERSION="${cursha}" # log the repo state [[ ${EGIT_COMMIT} != ${EGIT_BRANCH} ]] \ && echo " commit: ${EGIT_COMMIT}" echo " branch: ${EGIT_BRANCH}" echo " storage directory: \"${EGIT_DIR}\"" echo " checkout type: ${repo_type}" } # @FUNCTION: git_bootstrap # @DESCRIPTION: # Internal function that runs bootstrap command on unpacked source. git-2_bootstrap() { debug-print-function ${FUNCNAME} "$@" # @ECLASS-VARIABLE: EGIT_BOOTSTRAP # @DESCRIPTION: # Command to be executed after checkout and clone of the specified # repository. # enviroment the package will fail if there is no update, thus in # combination with --keep-going it would lead in not-updating # pakcages that are up-to-date. if [[ -n ${EGIT_BOOTSTRAP} ]]; then pushd "${EGIT_SOURCEDIR}" > /dev/null einfo "Starting bootstrap" if [[ -f ${EGIT_BOOTSTRAP} ]]; then # we have file in the repo which we should execute debug-print "${FUNCNAME}: bootstraping with file \"${EGIT_BOOTSTRAP}\"" if [[ -x ${EGIT_BOOTSTRAP} ]]; then eval "./${EGIT_BOOTSTRAP}" \ || die "${FUNCNAME}: bootstrap script failed" else eerror "\"${EGIT_BOOTSTRAP}\" is not executable." eerror "Report upstream, or bug ebuild maintainer to remove bootstrap command." die "\"${EGIT_BOOTSTRAP}\" is not executable" fi else # we execute some system command debug-print "${FUNCNAME}: bootstraping with commands \"${EGIT_BOOTSTRAP}\"" eval "${EGIT_BOOTSTRAP}" \ || die "${FUNCNAME}: bootstrap commands failed" fi einfo "Bootstrap finished" popd > /dev/null fi } # @FUNCTION: git-2_migrate_repository # @DESCRIPTION: # Internal function migrating between bare and normal checkout repository. # This is based on usage of EGIT_SUBMODULES, at least until they # start to work with bare checkouts sanely. # This function also set some global variables that differ between # bare and non-bare checkout. git-2_migrate_repository() { debug-print-function ${FUNCNAME} "$@" local target returnstate # first find out if we have submodules if [[ -z ${EGIT_HAS_SUBMODULES} ]]; then target="bare" else target="full" fi # check if user didn't specify that we want non-bare repo if [[ -n ${EGIT_NONBARE} ]]; then target="full" EGIT_LOCAL_NONBARE="true" fi # test if we already have some repo and if so find out if we have # to migrate the data if [[ -d ${EGIT_DIR} ]]; then if [[ ${target} == bare && -d ${EGIT_DIR}/.git ]]; then debug-print "${FUNCNAME}: converting \"${EGIT_DIR}\" to bare copy" ebegin "Converting \"${EGIT_DIR}\" from non-bare to bare copy" mv "${EGIT_DIR}/.git" "${EGIT_DIR}.bare" export GIT_DIR="${EGIT_DIR}.bare" git config core.bare true > /dev/null returnstate=$? unset GIT_DIR rm -rf "${EGIT_DIR}" mv "${EGIT_DIR}.bare" "${EGIT_DIR}" eend ${returnstate} fi if [[ ${target} == full && ! -d ${EGIT_DIR}/.git ]]; then debug-print "${FUNCNAME}: converting \"${EGIT_DIR}\" to non-bare copy" ebegin "Converting \"${EGIT_DIR}\" from bare to non-bare copy" git clone -l "${EGIT_DIR}" "${EGIT_DIR}.nonbare" > /dev/null returnstate=$? rm -rf "${EGIT_DIR}" mv "${EGIT_DIR}.nonbare" "${EGIT_DIR}" eend ${returnstate} fi fi if [[ ${returnstate} -ne 0 ]]; then debug-print "${FUNCNAME}: converting \"${EGIT_DIR}\" failed, removing to start from scratch" # migration failed, remove the EGIT_DIR to play it safe einfo "Migration failed, removing \"${EGIT_DIR}\" to start from scratch." rm -rf "${EGIT_DIR}" fi # set various options to work with both targets if [[ ${target} == bare ]]; then debug-print "${FUNCNAME}: working in bare repository for \"${EGIT_DIR}\"" EGIT_LOCAL_OPTIONS+="${EGIT_OPTIONS} --bare" MOVE_COMMAND="git clone -l -s -n ${EGIT_DIR// /\\ }" EGIT_UPDATE_CMD="git fetch -t -f -u origin ${EGIT_BRANCH}:${EGIT_BRANCH}" UPSTREAM_BRANCH="${EGIT_BRANCH}" else debug-print "${FUNCNAME}: working in bare repository for non-bare \"${EGIT_DIR}\"" MOVE_COMMAND="cp -pPR ." EGIT_LOCAL_OPTIONS="${EGIT_OPTIONS}" EGIT_UPDATE_CMD="git pull -f -u ${EGIT_OPTIONS}" UPSTREAM_BRANCH="origin/${EGIT_BRANCH}" EGIT_LOCAL_NONBARE="true" fi } # @FUNCTION: git-2_cleanup # @DESCRIPTION: # Internal function cleaning up all the global variables # that are not required after the unpack has been done. git-2_cleanup() { debug-print-function ${FUNCNAME} "$@" # Here we can unset only variables that are GLOBAL # defined by the eclass, BUT NOT subject to change # by user (like EGIT_PROJECT). # If ebuild writer polutes his environment it is # his problem only. unset EGIT_DIR unset MOVE_COMMAND unset EGIT_LOCAL_OPTIONS unset EGIT_UPDATE_CMD unset UPSTREAM_BRANCH unset EGIT_LOCAL_NONBARE } # @FUNCTION: git-2_src_unpack # @DESCRIPTION: # Default git src_unpack function. git-2_src_unpack() { debug-print-function ${FUNCNAME} "$@" git-2_init_variables git-2_prepare_storedir git-2_migrate_repository git-2_fetch "$@" git-2_gc git-2_submodules git-2_move_source git-2_branch git-2_bootstrap git-2_cleanup echo ">>> Unpacked to ${EGIT_SOURCEDIR}" # Users can specify some SRC_URI and we should # unpack the files too. if [[ -z ${EGIT_NOUNPACK} ]]; then if has ${EAPI:-0} 0 1; then [[ -n ${A} ]] && unpack ${A} else default_src_unpack fi fi }