[prev in list] [next in list] [prev in thread] [next in thread] 

List:       gentoo-dev
Subject:    Re: [gentoo-dev] git-r3: initial draft for review [v3]
From:       Michał Górny <mgorny () gentoo ! org>
Date:       2013-08-31 20:59:43
Message-ID: 20130831225943.42f66018 () gentoo ! org
[Download RAW message or body]

[Attachment #2 (multipart/mixed)]


A few more changes.

1. Added EGIT_CHECKOUT_DIR to control the place where stuff is checked
   out.

2. Moved the defaults from src_fetch() and src_unpack()
   into git-r3_fetch() and git-r3_checkout(). This makes using them
   easier, like:

   git-r3_fetch http://repo1
   git-r3_fetch http://repo2
   git-r3_checkout http://repo1
   git-r3_checkout http://repo2 ${S}/foo

3. Added prototype pkg_isuptodate() function [bug 482666 [1]].

   (I will update it when we decide on how the func should be named :))

As an additional testing tool, I attached the version of git-r3
converted to git-2 compatible API. IOW, a drop-in replacement that you
could use to test the new eclass internals with API almost fully
compatible with the old one.

[1]:https://bugs.gentoo.org/show_bug.cgi?id=482666

-- 
Best regards,
Michał Górny

[Attachment #5 (text/x-patch)]

diff --git a/gx86/eclass/git-r3.eclass b/gx86/eclass/git-r3.eclass
index d56c818..84c04c7 100644
--- a/gx86/eclass/git-r3.eclass
+++ b/gx86/eclass/git-r3.eclass
@@ -71,6 +71,12 @@ if [[ ! ${_GIT_R3} ]]; then
 #
 # It can be overriden via env using ${PN}_LIVE_COMMIT variable.
 
+# @ECLASS-VARIABLE: EGIT_CHECKOUT_DIR
+# @DESCRIPTION:
+# The directory to check the git sources out to.
+#
+# EGIT_CHECKOUT_DIR=${WORKDIR}/${P}
+
 # @ECLASS-VARIABLE: EGIT_NONSHALLOW
 # @DEFAULT_UNSET
 # @DESCRIPTION:
@@ -94,8 +100,6 @@ _git-r3_env_setup() {
 
 	livevar=${esc_pn}_LIVE_REPO
 	EGIT_REPO_URI=${!livevar:-${EGIT_REPO_URI}}
-	[[ ${EGIT_REPO_URI} ]] \
-		|| die "EGIT_REPO_URI must be set to a non-empty value"
 	[[ ${!livevar} ]] \
 		&& ewarn "Using ${livevar}, no support will be provided"
 
@@ -209,26 +213,44 @@ _git-r3_set_submodules() {
 }
 
 # @FUNCTION: git-r3_fetch
-# @USAGE: <repo-uri> <remote-ref> <local-id>
+# @USAGE: [<repo-uri> [<remote-ref> [<local-id>]]]
 # @DESCRIPTION:
-# Fetch new commits to the local clone of repository. <repo-uri> follows
-# the syntax of EGIT_REPO_URI and may list multiple (fallback) URIs.
-# <remote-ref> specifies the remote ref to fetch (branch, tag
-# or commit). <local-id> specifies an identifier that needs to uniquely
-# identify the fetch operation in case multiple parallel merges used
-# the git repo. <local-id> usually involves using CATEGORY, PN and SLOT.
+# 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}.
 #
-# The fetch operation will only affect the local storage. It will not
-# touch the working copy. If the repository contains submodules, they
-# will be fetched recursively as well.
+# <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.
+#
+# 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} "$@"
 
-	local repos=( ${1} )
-	local remote_ref=${2}
-	local local_id=${3}
+	local repos=( ${1:-${EGIT_REPO_URI}} )
+	local branch=${EGIT_BRANCH:+refs/heads/${EGIT_BRANCH}}
+	local remote_ref=${2:-${EGIT_COMMIT:-${branch:-HEAD}}}
+	local local_id=${3:-${CATEGORY}/${PN}/${SLOT}}
 	local local_ref=refs/heads/${local_id}/__main__
 
+	[[ ${repos[@]} ]] || die "No URI provided and EGIT_REPO_URI unset"
+
 	local -x GIT_DIR
 	_git-r3_set_gitdir ${repos[0]}
 
@@ -282,7 +304,7 @@ git-r3_fetch() {
 			if [[ ${is_branch} ]]; then
 				ref_param+=( -f "${remote_ref}:${local_id}/__main__" )
 			else
-				ref_param+=( "${remote_ref}" )
+				ref_param+=( "refs/tags/${remote_ref}" )
 			fi
 		fi
 
@@ -331,22 +353,31 @@ git-r3_fetch() {
 }
 
 # @FUNCTION: git-r3_checkout
-# @USAGE: <repo-uri> <local-id> <path>
+# @USAGE: [<repo-uri> [<checkout-path> [<local-id>]]]
 # @DESCRIPTION:
-# Check the previously fetched commit out to <path> (usually
-# ${WORKDIR}/${P}). <repo-uri> follows the syntax of EGIT_REPO_URI
-# and will be used to re-construct the local storage path. <local-id>
-# is the unique identifier used for the fetch operation and will
-# be used to obtain the proper commit.
+# 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}.
 #
-# If the repository contains submodules, they will be checked out
-# recursively as well.
+# <local-id> needs to specify the local identifier that was used
+# for respective git-r3_fetch.
+#
+# 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=( ${1} )
-	local local_id=${2}
-	local out_dir=${3}
+	local repos=( ${1:-${EGIT_REPO_URI}} )
+	local out_dir=${2:-${EGIT_CHECKOUT_DIR:-${WORKDIR}/${P}}}
+	local local_id=${3:-${CATEGORY}/${PN}/${SLOT}}
 
 	local -x GIT_DIR GIT_WORK_TREE
 	_git-r3_set_gitdir ${repos[0]}
@@ -396,8 +427,8 @@ git-r3_checkout() {
 			local url=${submodules[1]}
 			local path=${submodules[2]}
 
-			git-r3_checkout "${url}" "${local_id}/${subname}" \
-				"${GIT_WORK_TREE}/${path}"
+			git-r3_checkout "${url}" "${GIT_WORK_TREE}/${path}" \
+				"${local_id}/${subname}"
 
 			submodules=( "${submodules[@]:3}" ) # shift
 		done
@@ -408,16 +439,72 @@ git-r3_checkout() {
 	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=( ${1:-${EGIT_REPO_URI}} )
+	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 is_branch lookup_ref
+		if [[ ${remote_ref} == refs/heads/* || ${remote_ref} == HEAD ]]
+		then
+			is_branch=1
+			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} "$@"
 
 	[[ ${EVCS_OFFLINE} ]] && return
 
 	_git-r3_env_setup
-	local branch=${EGIT_BRANCH:+refs/heads/${EGIT_BRANCH}}
-	git-r3_fetch "${EGIT_REPO_URI}" \
-		"${EGIT_COMMIT:-${branch:-HEAD}}" \
-		${CATEGORY}/${PN}/${SLOT}
+	git-r3_fetch
 }
 
 git-r3_src_unpack() {
@@ -425,9 +512,16 @@ git-r3_src_unpack() {
 
 	_git-r3_env_setup
 	git-r3_src_fetch
-	git-r3_checkout "${EGIT_REPO_URI}" \
-		${CATEGORY}/${PN}/${SLOT} \
-		"${WORKDIR}/${P}"
+	git-r3_checkout
+}
+
+git-r3_pkg_isuptodate() {
+	debug-print-function ${FUNCNAME} "$@"
+
+	local new_commit_id=$(git-r3_peek_remote_ref)
+	ewarn "old: ${EGIT_VERSION}"
+	ewarn "new: ${new_commit_id}"
+	[[ ! ${new_commit_id} || ${EGIT_VERSION} == ${new_commit_id} ]]
 }
 
 _GIT_R3=1

[Attachment #6 (text/plain)]

# Copyright 1999-2013 Gentoo Foundation
# Distributed under the terms of the GNU General Public License v2
# $Header: $

# @ECLASS: git-r3.eclass
# @MAINTAINER:
# Michał Górny <mgorny@gentoo.org>
# @BLURB: Eclass for fetching and unpacking git repositories.
# @DESCRIPTION:
# Third generation eclass for easing maitenance of live ebuilds using
# git as remote repository. Eclass supports lightweight (shallow)
# clones, local object deduplication and submodules.

case "${EAPI:-0}" in
	0|1|2|3|4|5)
		;;
	*)
		die "Unsupported EAPI=${EAPI} (unknown) for ${ECLASS}"
		;;
esac

if [[ ! ${_GIT_R3} ]]; then

inherit eutils

fi

EXPORT_FUNCTIONS src_unpack

if [[ ! ${_GIT_R3} ]]; then

# @ECLASS-VARIABLE: EGIT_STORE_DIR
# @DESCRIPTION:
# Storage directory for git sources.
#
# EGIT_STORE_DIR=${DISTDIR}/git3-src

# @ECLASS-VARIABLE: EGIT_REPO_URI
# @REQUIRED
# @DESCRIPTION:
# URIs to the repository, e.g. git://foo, https://foo. If multiple URIs
# are provided, the eclass will consider them as fallback URIs to try
# if the first URI does not work.
#
# It can be overriden via env using ${PN}_LIVE_REPO variable.
#
# Example:
# @CODE
# EGIT_REPO_URI="git://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: EGIT_BRANCH
# @DEFAULT_UNSET
# @DESCRIPTION:
# The branch name to check out. If unset, the upstream default (HEAD)
# will be used.
#
# It can be overriden via env using ${PN}_LIVE_BRANCH variable.

# @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. If set, EGIT_BRANCH will
# be ignored.
#
# It can be overriden via env using ${PN}_LIVE_COMMIT variable.

# @ECLASS-VARIABLE: EGIT_CHECKOUT_DIR
# @DESCRIPTION:
# The directory to check the git sources out to.
#
# EGIT_CHECKOUT_DIR=${WORKDIR}/${P}

# @ECLASS-VARIABLE: EGIT_NONSHALLOW
# @DEFAULT_UNSET
# @DESCRIPTION:
# Disable performing shallow fetches/clones. Shallow clones have
# a fair number of limitations. Therefore, if you'd like the eclass to
# perform complete clones instead, set this to a non-null value.
#
# This variable is to be set in make.conf. Ebuilds are not allowed
# to set it.

# @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} "$@"

	local esc_pn livevar
	esc_pn=${PN//[-+]/_}

	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"

	# git-2 unsupported cruft
	local v
	for v in EGIT_{SOURCEDIR,MASTER,HAS_SUBMODULES,PROJECT} \
			EGIT_{NOUNPACK,BOOTSTRAP}
	do
		[[ ${!v} ]] && die "${v} is not supported."
	done
}

# @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 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
		# 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/};;
		# google code, 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}}
	: ${EGIT_STORE_DIR:=${distdir}/git3-src}

	GIT_DIR=${EGIT_STORE_DIR}/${repo_name}

	if [[ ! -d ${EGIT_STORE_DIR} ]]; then
		(
			addwrite /
			mkdir -m0755 -p "${EGIT_STORE_DIR}"
		) || die "Unable to create ${EGIT_STORE_DIR}"
	fi

	addwrite "${EGIT_STORE_DIR}"
	if [[ ! -d ${GIT_DIR} ]]; then
		mkdir "${GIT_DIR}" || die
		git init --bare || die

		# avoid auto-unshallow :)
		touch "${GIT_DIR}"/shallow || die
	fi
}

# @FUNCTION: _git-r3_set_submodules
# @USAGE: <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.
_git-r3_set_submodules() {
	debug-print-function ${FUNCNAME} "$@"

	local data=${1}

	# ( 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=*}

		submodules+=(
			"${subname}"
			"$(echo "${data}" | git config -f /dev/fd/0 \
				submodule."${subname}".url)"
			"$(echo "${data}" | git config -f /dev/fd/0 \
				submodule."${subname}".path)"
		)
	done < <(echo "${data}" | git config -f /dev/fd/0 -l)
}

# @FUNCTION: git-r3_fetch
# @USAGE: [<repo-uri> [<remote-ref> [<local-id>]]]
# @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.
#
# 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} "$@"

	local repos=( ${1:-${EGIT_REPO_URI}} )
	local branch=${EGIT_BRANCH:+refs/heads/${EGIT_BRANCH}}
	local remote_ref=${2:-${EGIT_COMMIT:-${branch:-HEAD}}}
	local local_id=${3:-${CATEGORY}/${PN}/${SLOT}}
	local local_ref=refs/heads/${local_id}/__main__

	[[ ${repos[@]} ]] || die "No URI provided and EGIT_REPO_URI unset"

	local -x GIT_DIR
	_git-r3_set_gitdir ${repos[0]}

	# try to fetch from the remote
	local r success
	for r in ${repos[@]}; do
		einfo "Fetching ${remote_ref} from ${r} ..."

		local is_branch lookup_ref
		if [[ ${remote_ref} == refs/heads/* || ${remote_ref} == HEAD ]]
		then
			is_branch=1
			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

		# first, try ls-remote to see if ${remote_ref} is a real ref
		# and not a commit id. if it succeeds, we can pass ${remote_ref}
		# to 'fetch'. otherwise, we will just fetch everything

		# split on whitespace
		local ref=(
			$(git ls-remote "${r}" "${lookup_ref}")
		)

		# now, another important thing. we may only fetch a remote
		# branch directly to a local branch. Otherwise, we need to fetch
		# the commit and re-create the branch on top of it.

		local ref_param=()
		if [[ ! ${ref[0]} ]]; then
			local EGIT_NONSHALLOW=1
		fi

		if [[ ! -f ${GIT_DIR}/shallow ]]; then
			# if it's a complete repo, fetch it as-is
			:
		elif [[ ${EGIT_NONSHALLOW} ]]; then
			# if it's a shallow clone but we need complete,
			# unshallow it
			ref_param+=( --unshallow )
		else
			# otherwise, just fetch as shallow
			ref_param+=( --depth 1 )
		fi

		if [[ ${ref[0]} ]]; then
			if [[ ${is_branch} ]]; then
				ref_param+=( -f "${remote_ref}:${local_id}/__main__" )
			else
				ref_param+=( "refs/tags/${remote_ref}" )
			fi
		fi

		# if ${remote_ref} is branch or tag, ${ref[@]} will contain
		# the respective commit id. otherwise, it will be an empty
		# array, so the following won't evaluate to a parameter.
		set -- git fetch --no-tags "${r}" "${ref_param[@]}"
		echo "${@}" >&2
		if "${@}"; then
			if [[ ! ${is_branch} ]]; then
				set -- git branch -f "${local_id}/__main__" \
					"${ref[0]:-${remote_ref}}"
				echo "${@}" >&2
				if ! "${@}"; then
					die "Creating branch for ${remote_ref} failed (wrong ref?)."
				fi
			fi

			success=1
			break
		fi
	done
	[[ ${success} ]] || die "Unable to fetch from any of EGIT_REPO_URI"

	# recursively fetch submodules
	if git cat-file -e "${local_ref}":.gitmodules &>/dev/null; then
		local submodules
		_git-r3_set_submodules \
			"$(git cat-file -p "${local_ref}":.gitmodules || die)"

		while [[ ${submodules[@]} ]]; do
			local subname=${submodules[0]}
			local url=${submodules[1]}
			local path=${submodules[2]}
			local commit=$(git rev-parse "${local_ref}:${path}")

			if [[ ! ${commit} ]]; then
				die "Unable to get commit id for submodule ${subname}"
			fi

			git-r3_fetch "${url}" "${commit}" "${local_id}/${subname}"

			submodules=( "${submodules[@]:3}" ) # shift
		done
	fi
}

# @FUNCTION: git-r3_checkout
# @USAGE: [<repo-uri> [<checkout-path> [<local-id>]]]
# @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.
#
# 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=( ${1:-${EGIT_REPO_URI}} )
	local out_dir=${2:-${EGIT_CHECKOUT_DIR:-${WORKDIR}/${P}}}
	local local_id=${3:-${CATEGORY}/${PN}/${SLOT}}

	local -x GIT_DIR GIT_WORK_TREE
	_git-r3_set_gitdir ${repos[0]}
	GIT_WORK_TREE=${out_dir}

	einfo "Checking out ${repos[0]} to ${out_dir} ..."

	mkdir -p "${GIT_WORK_TREE}"
	set -- git checkout -f "${local_id}"/__main__ .
	echo "${@}" >&2
	"${@}" || die "git checkout ${local_id}/__main__ failed"

	# diff against previous revision (if any)
	local new_commit_id=$(git rev-parse --verify "${local_id}"/__main__)
	local old_commit_id=$(
		git rev-parse --verify "${local_id}"/__old__ 2>/dev/null
	)

	if [[ ! ${old_commit_id} ]]; then
		echo "GIT NEW branch -->"
		echo "   repository:               ${repos[0]}"
		echo "   at the commit:            ${new_commit_id}"
	else
		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}"

			git --no-pager diff --color --stat \
				${old_commit_id}..${new_commit_id}
		else
			echo "   at the commit:            ${new_commit_id}"
		fi
	fi
	git branch -f "${local_id}"/{__old__,__main__} || die

	# recursively checkout submodules
	if [[ -f ${GIT_WORK_TREE}/.gitmodules ]]; then
		local submodules
		_git-r3_set_submodules \
			"$(cat "${GIT_WORK_TREE}"/.gitmodules)"

		while [[ ${submodules[@]} ]]; do
			local subname=${submodules[0]}
			local url=${submodules[1]}
			local path=${submodules[2]}

			git-r3_checkout "${url}" "${GIT_WORK_TREE}/${path}" \
				"${local_id}/${subname}"

			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=( ${1:-${EGIT_REPO_URI}} )
	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 is_branch lookup_ref
		if [[ ${remote_ref} == refs/heads/* || ${remote_ref} == HEAD ]]
		then
			is_branch=1
			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} "$@"

	[[ ${EVCS_OFFLINE} ]] && return

	_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
}

git-r3_pkg_isuptodate() {
	debug-print-function ${FUNCNAME} "$@"

	local new_commit_id=$(git-r3_peek_remote_ref)
	ewarn "old: ${EGIT_VERSION}"
	ewarn "new: ${new_commit_id}"
	[[ ! ${new_commit_id} || ${EGIT_VERSION} == ${new_commit_id} ]]
}

_GIT_R3=1
fi

[Attachment #7 (text/plain)]

# Copyright 1999-2013 Gentoo Foundation
# Distributed under the terms of the GNU General Public License v2
# $Header: $

# @ECLASS: git-r3.eclass
# @MAINTAINER:
# Michał Górny <mgorny@gentoo.org>
# @BLURB: Eclass for fetching and unpacking git repositories.
# @DESCRIPTION:
# Third generation eclass for easing maitenance of live ebuilds using
# git as remote repository. Eclass supports lightweight (shallow)
# clones, local object deduplication and submodules.

case "${EAPI:-0}" in
	0|1|2|3|4|5)
		;;
	*)
		die "Unsupported EAPI=${EAPI} (unknown) for ${ECLASS}"
		;;
esac

if [[ ! ${_GIT_R3} ]]; then

inherit eutils

fi

EXPORT_FUNCTIONS src_unpack

if [[ ! ${_GIT_R3} ]]; then

# @ECLASS-VARIABLE: EGIT_STORE_DIR
# @DESCRIPTION:
# Storage directory for git sources.
#
# EGIT_STORE_DIR=${DISTDIR}/git3-src

# @ECLASS-VARIABLE: EGIT_REPO_URI
# @REQUIRED
# @DESCRIPTION:
# URIs to the repository, e.g. git://foo, https://foo. If multiple URIs
# are provided, the eclass will consider them as fallback URIs to try
# if the first URI does not work.
#
# It can be overriden via env using ${PN}_LIVE_REPO variable.
#
# Example:
# @CODE
# EGIT_REPO_URI="git://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: EGIT_BRANCH
# @DEFAULT_UNSET
# @DESCRIPTION:
# The branch name to check out. If unset, the upstream default (HEAD)
# will be used.
#
# It can be overriden via env using ${PN}_LIVE_BRANCH variable.

# @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. If set, EGIT_BRANCH will
# be ignored.
#
# It can be overriden via env using ${PN}_LIVE_COMMIT variable.

# @ECLASS-VARIABLE: EGIT_CHECKOUT_DIR
# @DESCRIPTION:
# The directory to check the git sources out to.
#
# EGIT_CHECKOUT_DIR=${WORKDIR}/${P}

# @ECLASS-VARIABLE: EGIT_NONSHALLOW
# @DEFAULT_UNSET
# @DESCRIPTION:
# Disable performing shallow fetches/clones. Shallow clones have
# a fair number of limitations. Therefore, if you'd like the eclass to
# perform complete clones instead, set this to a non-null value.
#
# This variable is to be set in make.conf. Ebuilds are not allowed
# to set it.

# @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} "$@"

	local esc_pn livevar
	esc_pn=${PN//[-+]/_}

	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"

	# git-2 compat
	: ${EGIT_BRANCH:=${EGIT_MASTER}}
	: ${EGIT_CHECKOUT_DIR:=${EGIT_SOURCEDIR}}

	[[ ${EGIT_BOOTSTRAP} ]] && die "EGIT_BOOTSTRAP not supported."
}

# @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 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
		# 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/};;
		# google code, 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}}
	: ${EGIT_STORE_DIR:=${distdir}/git3-src}

	GIT_DIR=${EGIT_STORE_DIR}/${repo_name}

	if [[ ! -d ${EGIT_STORE_DIR} ]]; then
		(
			addwrite /
			mkdir -m0755 -p "${EGIT_STORE_DIR}"
		) || die "Unable to create ${EGIT_STORE_DIR}"
	fi

	addwrite "${EGIT_STORE_DIR}"
	if [[ ! -d ${GIT_DIR} ]]; then
		mkdir "${GIT_DIR}" || die
		git init --bare || die

		# avoid auto-unshallow :)
		touch "${GIT_DIR}"/shallow || die
	fi
}

# @FUNCTION: _git-r3_set_submodules
# @USAGE: <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.
_git-r3_set_submodules() {
	debug-print-function ${FUNCNAME} "$@"

	local data=${1}

	# ( 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=*}

		submodules+=(
			"${subname}"
			"$(echo "${data}" | git config -f /dev/fd/0 \
				submodule."${subname}".url)"
			"$(echo "${data}" | git config -f /dev/fd/0 \
				submodule."${subname}".path)"
		)
	done < <(echo "${data}" | git config -f /dev/fd/0 -l)
}

# @FUNCTION: git-r3_fetch
# @USAGE: [<repo-uri> [<remote-ref> [<local-id>]]]
# @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.
#
# 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} "$@"

	local repos=( ${1:-${EGIT_REPO_URI}} )
	local branch=${EGIT_BRANCH:+refs/heads/${EGIT_BRANCH}}
	local remote_ref=${2:-${EGIT_COMMIT:-${branch:-HEAD}}}
	local local_id=${3:-${CATEGORY}/${PN}/${SLOT}}
	local local_ref=refs/heads/${local_id}/__main__

	[[ ${repos[@]} ]] || die "No URI provided and EGIT_REPO_URI unset"

	local -x GIT_DIR
	_git-r3_set_gitdir ${repos[0]}

	# try to fetch from the remote
	local r success
	for r in ${repos[@]}; do
		einfo "Fetching ${remote_ref} from ${r} ..."

		local is_branch lookup_ref
		if [[ ${remote_ref} == refs/heads/* || ${remote_ref} == HEAD ]]
		then
			is_branch=1
			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

		# first, try ls-remote to see if ${remote_ref} is a real ref
		# and not a commit id. if it succeeds, we can pass ${remote_ref}
		# to 'fetch'. otherwise, we will just fetch everything

		# split on whitespace
		local ref=(
			$(git ls-remote "${r}" "${lookup_ref}")
		)

		# now, another important thing. we may only fetch a remote
		# branch directly to a local branch. Otherwise, we need to fetch
		# the commit and re-create the branch on top of it.

		local ref_param=()
		if [[ ! ${ref[0]} ]]; then
			local EGIT_NONSHALLOW=1
		fi

		if [[ ! -f ${GIT_DIR}/shallow ]]; then
			# if it's a complete repo, fetch it as-is
			:
		elif [[ ${EGIT_NONSHALLOW} ]]; then
			# if it's a shallow clone but we need complete,
			# unshallow it
			ref_param+=( --unshallow )
		else
			# otherwise, just fetch as shallow
			ref_param+=( --depth 1 )
		fi

		if [[ ${ref[0]} ]]; then
			if [[ ${is_branch} ]]; then
				ref_param+=( -f "${remote_ref}:${local_id}/__main__" )
			else
				ref_param+=( "refs/tags/${remote_ref}" )
			fi
		fi

		# if ${remote_ref} is branch or tag, ${ref[@]} will contain
		# the respective commit id. otherwise, it will be an empty
		# array, so the following won't evaluate to a parameter.
		set -- git fetch --no-tags "${r}" "${ref_param[@]}"
		echo "${@}" >&2
		if "${@}"; then
			if [[ ! ${is_branch} ]]; then
				set -- git branch -f "${local_id}/__main__" \
					"${ref[0]:-${remote_ref}}"
				echo "${@}" >&2
				if ! "${@}"; then
					die "Creating branch for ${remote_ref} failed (wrong ref?)."
				fi
			fi

			success=1
			break
		fi
	done
	[[ ${success} ]] || die "Unable to fetch from any of EGIT_REPO_URI"

	# recursively fetch submodules
	if git cat-file -e "${local_ref}":.gitmodules &>/dev/null; then
		local submodules
		_git-r3_set_submodules \
			"$(git cat-file -p "${local_ref}":.gitmodules || die)"

		while [[ ${submodules[@]} ]]; do
			local subname=${submodules[0]}
			local url=${submodules[1]}
			local path=${submodules[2]}
			local commit=$(git rev-parse "${local_ref}:${path}")

			if [[ ! ${commit} ]]; then
				die "Unable to get commit id for submodule ${subname}"
			fi

			git-r3_fetch "${url}" "${commit}" "${local_id}/${subname}"

			submodules=( "${submodules[@]:3}" ) # shift
		done
	fi
}

# @FUNCTION: git-r3_checkout
# @USAGE: [<repo-uri> [<checkout-path> [<local-id>]]]
# @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.
#
# 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=( ${1:-${EGIT_REPO_URI}} )
	local out_dir=${2:-${EGIT_CHECKOUT_DIR:-${WORKDIR}/${P}}}
	local local_id=${3:-${CATEGORY}/${PN}/${SLOT}}

	local -x GIT_DIR GIT_WORK_TREE
	_git-r3_set_gitdir ${repos[0]}
	GIT_WORK_TREE=${out_dir}

	einfo "Checking out ${repos[0]} to ${out_dir} ..."

	mkdir -p "${GIT_WORK_TREE}"
	set -- git checkout -f "${local_id}"/__main__ .
	echo "${@}" >&2
	"${@}" || die "git checkout ${local_id}/__main__ failed"

	# diff against previous revision (if any)
	local new_commit_id=$(git rev-parse --verify "${local_id}"/__main__)
	local old_commit_id=$(
		git rev-parse --verify "${local_id}"/__old__ 2>/dev/null
	)

	if [[ ! ${old_commit_id} ]]; then
		echo "GIT NEW branch -->"
		echo "   repository:               ${repos[0]}"
		echo "   at the commit:            ${new_commit_id}"
	else
		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}"

			git --no-pager diff --color --stat \
				${old_commit_id}..${new_commit_id}
		else
			echo "   at the commit:            ${new_commit_id}"
		fi
	fi
	git branch -f "${local_id}"/{__old__,__main__} || die

	# recursively checkout submodules
	if [[ -f ${GIT_WORK_TREE}/.gitmodules ]]; then
		local submodules
		_git-r3_set_submodules \
			"$(cat "${GIT_WORK_TREE}"/.gitmodules)"

		while [[ ${submodules[@]} ]]; do
			local subname=${submodules[0]}
			local url=${submodules[1]}
			local path=${submodules[2]}

			git-r3_checkout "${url}" "${GIT_WORK_TREE}/${path}" \
				"${local_id}/${subname}"

			submodules=( "${submodules[@]:3}" ) # shift
		done
	fi

	# keep this *after* submodules
	export EGIT_DIR=${GIT_DIR}
	export EGIT_VERSION=${new_commit_id}
}

git-r3_src_fetch() {
	debug-print-function ${FUNCNAME} "$@"

	[[ ${EVCS_OFFLINE} ]] && return

	_git-r3_env_setup
	git-r3_fetch
}

git-2_src_unpack() {
	debug-print-function ${FUNCNAME} "$@"

	_git-r3_env_setup
	git-r3_src_fetch
	git-r3_checkout
}

_GIT_R3=1
fi

["signature.asc" (application/pgp-signature)]

[prev in list] [next in list] [prev in thread] [next in thread] 

Configure | About | News | Add a list | Sponsored by KoreLogic