#!/bin/sh git_cache() { git -C "${GIT_CACHE_DIR}" $* } git_cache_initialized() { local _git_dir="$(git_cache rev-parse --git-dir 2>/dev/null)" test "$_git_dir" = "." -o "$_git_dir" = ".git" } init() { set -e git_cache_initialized || { mkdir -p "${GIT_CACHE_DIR}" git_cache init --bare git_cache config core.compression 1 } set +e } add() { local repo="$1" _locked "$GIT_CACHE_DIR/$name.addlock" _add "$repo" } _add() { set -e local repo="$1" local name="$(_remote_name $repo)" if ! is_cached "$repo"; then git_cache remote add "$name" "$repo" git_cache config --add remote.${name}.fetch "+refs/tags/*:refs/tags/${name}/*" else echo "git-cache: $url already in cache" fi set +e } if [ "$(uname)" = Darwin ]; then _locked() { local lockfile="$1" shift while ! shlock -p $$ -f $lockfile; do sleep 0.2 done $* rm $lockfile } else _locked() { local lockfile="$1" shift ( flock -w 600 9 || exit 1 $* ) 9>"$lockfile" } fi update() { set -e local REMOTE=${1} if [ -n "$REMOTE" ]; then local REMOTES=$(_remote_name $REMOTE) else local REMOTES="$(git_cache remote show)" fi for remote in $REMOTES; do echo "git-cache: updating remote $remote" _locked "$GIT_CACHE_DIR/$remote.lock" git_cache --namespace $remote fetch -n $remote done set +e } is_cached() { set +e local url="$1" local REMOTES="$(git_cache remote show)" for remote in $REMOTES; do [ "$(git_cache ls-remote --get-url $remote)" = "$url" ] && return 0 done set -e return 1 } list() { local REMOTES="$(git_cache remote show)" for remote in $REMOTES; do echo "$(git_cache ls-remote --get-url $remote)" done } drop() { set -e local REMOTE=${1} [ -z "$REMOTE" ] && { echo "usage: git cache drop " exit 1 } local REMOTES="$(git_cache remote show)" for remote in $REMOTES; do [ "$(git_cache ls-remote --get-url $remote)" = "$REMOTE" ] && { git_cache remote remove $remote break } done set +e } _check_commit() { git_cache cat-file -e ${1}^{commit} 2>/dev/null } _remote_name() { echo "$*" | git hash-object --stdin } _tag_to_sha1() { local out="$(git_cache log -n 1 --pretty=oneline $1 -- 2>/dev/null)" [ -n "$out" ] && echo $out | cut -f 1 -d" " } _check_tag_or_commit() { local SHA1=$1 local REMOTE_NAME=$2 if _check_commit $SHA1 ; then git_cache tag commit$SHA1 $SHA1 2> /dev/null || true # ignore possibly already existing tag echo "commit$SHA1" elif _tag_to_sha1 ${REMOTE_NAME}/$SHA1 > /dev/null; then echo "${REMOTE_NAME}/$SHA1" fi } clone() { set -e local REMOTE="${1}" local SHA1="${2}" local REMOTE_NAME="$(_remote_name $REMOTE)" local TARGET_PATH="${3}" [ -z "$TARGET_PATH" ] && TARGET_PATH="$(basename $REMOTE)" # make sure git won't ask for credentials export GIT_TERMINAL_PROMPT=0 if git_cache_initialized; then if ! is_cached "$REMOTE"; then echo "git cache: auto-adding $REMOTE" add "$REMOTE" fi local tag="$(_check_tag_or_commit $SHA1 $REMOTE_NAME)" if [ -z "$tag" ]; then # commit / tag not in cache, try updating repo update "$REMOTE" || true tag="$(_check_tag_or_commit $SHA1 $REMOTE_NAME)" fi if [ -n "$tag" ]; then echo "git-cache: cloning from cache." git -c advice.detachedHead=false clone --reference "${GIT_CACHE_DIR}" --shared "${GIT_CACHE_DIR}" "${TARGET_PATH}" --branch $tag git -C "${TARGET_PATH}" fetch origin "refs/tags/${REMOTE_NAME}/*:refs/tags/*" > /dev/null else echo "git-cache: trying checkout from source" git clone --reference "${GIT_CACHE_DIR}" --shared "${REMOTE}" "${TARGET_PATH}" git -c advice.detachedHead=false -C "${TARGET_PATH}" checkout $SHA1 fi else git clone "${REMOTE}" "${TARGET_PATH}" git -c advice.detachedHead=false -C "${TARGET_PATH}" checkout $SHA1 fi set +e } usage() { echo "git cache uses a bare git repository containing all objects from multiple" echo "upstream git repositories." echo "" echo "usage:" echo "" echo " git cache init initialize git cache" echo " git cache add add repository " echo " git cache list list cached repositories" echo " git cache drop drop repo from cache" echo " git cache update [] fetch repo (or all)" echo " git cache clone clone repository from cache" echo " git cache show-path print's the path that can be used as " echo " '--reference' parameter" echo "" echo "To retrieve objects from cache (will use remote repository if needed):" echo ' git clone --reference $(git cache show-path) ' } [ $# -eq 0 ] && { usage exit 1 } ACTION=$1 shift 1 export GIT_CACHE_DIR=${GIT_CACHE_DIR:-${HOME}/.gitcache} case $ACTION in init) init $* ;; add) add $* ;; update) update $* ;; list) list $* ;; drop) drop $* ;; show-path) echo ${GIT_CACHE_DIR} ;; clone) clone $* ;; *) usage ;; esac