#!/bin/sh

GIT_NOTARY_VERSION=2.3.1 GIT_NOTARY_NAMESPACE=${GIT_NOTARY_NAMESPACE:-'versioning'}

# notes [branch] [base] [namespace] notes() {

BRANCH=${1-'develop'}
BASE=${2:-$(git describe --tags --abbrev=0)}
NAMESPACE=${3:-${GIT_NOTARY_NAMESPACE}}

git rev-list --topo-order ${BASE}..${BRANCH} --reverse | while read OBJECT; do
    printf "${OBJECT} "
    git notes --ref=${NAMESPACE} show ${OBJECT} 2> /dev/null || echo
done | grep -E '(MAJOR|MINOR|PATCH)$'

}

# squash squash() {

while read OBJECT_CHANGE; do
    OBJECT=$(echo ${OBJECT_CHANGE} | awk '{ print $1 }')
    CHANGE=$(echo ${OBJECT_CHANGE} | awk '{ print $2 }')

    case ${CHANGE} in
        MAJOR)
            RESULT=MAJOR;;
        MINOR)
            test "${RESULT}" != MAJOR && RESULT=MINOR;;
        PATCH)
            test -z "${RESULT}" && RESULT=PATCH;;
    esac
done

test ! -z "${RESULT}" && echo ${OBJECT} ${RESULT}

}

# squeeze <up|down> squeeze() {

case ${1} in
    d|down|f|first|o|old*)
        DIRECTION=DOWN;;
    u|up|l|last|n|new*)
        DIRECTION=UP;;
    *)
        echo 'git-notary squeeze requires a direction (up or down)' >&2
        exit 23;;
esac

while read OBJECT_CHANGE; do
    OBJECT=$(echo ${OBJECT_CHANGE} | awk '{ print $1 }')
    CHANGE=$(echo ${OBJECT_CHANGE} | awk '{ print $2 }')

    if test "${CHANGE}" != "${LAST_CHANGE}"; then
        case ${DIRECTION} in
            DOWN)
                echo ${OBJECT} ${CHANGE};;
            UP)
                test ! -z "${LAST_OBJECT}" && echo ${LAST_OBJECT} ${LAST_CHANGE};;
        esac
    fi

    LAST_OBJECT=${OBJECT}
    LAST_CHANGE=${CHANGE}
done

test "${DIRECTION}" = UP && echo ${LAST_OBJECT} ${LAST_CHANGE}

}

# versions [initial] versions() {

set -o errexit
VERSION=${1:-$(git describe --tags --abbrev=0)}

MAJOR=$(echo ${VERSION} | awk -F . '{ print $1 }')
MINOR=$(echo ${VERSION} | awk -F . '{ print $2 }')
PATCH=$(echo ${VERSION} | awk -F . '{ print $3 }')

next() {
    echo ${1} + 1 | bc
}

while read OBJECT_CHANGE; do
    OBJECT=$(echo ${OBJECT_CHANGE} | awk '{ print $1 }')
    CHANGE=$(echo ${OBJECT_CHANGE} | awk '{ print $2 }')

    case ${CHANGE} in
        MAJOR)
            MAJOR=$(next ${MAJOR})
            MINOR=0
            PATCH=0
            ;;
        MINOR)
            MINOR=$(next ${MINOR})
            PATCH=0
            ;;
        PATCH)
            PATCH=$(next ${PATCH})
            ;;
    esac

    VERSION=${MAJOR}.${MINOR}.${PATCH}
    echo ${OBJECT} ${VERSION}
done

}

# tags [–apply] tags() {

while read OBJECT_TAG; do
    OBJECT=$(echo ${OBJECT_TAG} | awk '{ print $1 }')
    TAG=$(echo ${OBJECT_TAG} | awk '{ print $2 }')

    if test "${1}" = '--apply'; then
        git tag ${TAG} ${OBJECT}
    else
        echo git tag ${TAG} ${OBJECT}
    fi
done

}

# fetch [remote] [namespace] fetch() {

FORCE=""
if test "${1:-}" = "--force"; then
    shift
    FORCE="+"
fi
REMOTE=${1:-'origin'}
NAMESPACE=${2:-${GIT_NOTARY_NAMESPACE}}

git fetch ${REMOTE} ${FORCE}refs/notes/${NAMESPACE}:refs/notes/${NAMESPACE}

}

# pull [remote] [namespace] [strategy] pull() {

REMOTE=${1:-'origin'}
NAMESPACE=${2:-${GIT_NOTARY_NAMESPACE}}
STRATEGY=${3:-'theirs'}

git fetch ${REMOTE} refs/notes/${NAMESPACE}
env GIT_NOTES_REF=refs/notes/${NAMESPACE} git notes merge -s ${STRATEGY} FETCH_HEAD

}

# push [remote] [namespace] push() {

REMOTE=${1:-'origin'}
NAMESPACE=${2:-${GIT_NOTARY_NAMESPACE}}

git push --no-verify ${REMOTE} refs/notes/${NAMESPACE}

}

# new <major|minor|patch> [object] [namespace] new() {

CHANGE=$(echo ${1} | tr [:lower:] [:upper:])
OBJECT=${2:-HEAD}
NAMESPACE=${3:-${GIT_NOTARY_NAMESPACE}}

if echo ${CHANGE} | grep -qE '^(MAJOR|MINOR|PATCH)$'; then
    git notes --ref=${NAMESPACE} add --message ${CHANGE} ${OBJECT}
else
    echo MAJOR MINOR and PATCH are valid. ${CHANGE} is not.
    exit 23
fi

}

# delta (–squash) [object] [base] [namespace] delta() {

if test "${1}" = '--squash'; then
   SQUASH=true
   shift
fi

OBJECT=${1:-HEAD}
BASE=${2:-$(git describe --tags --abbrev=0)}
NAMESPACE=${3:-${GIT_NOTARY_NAMESPACE}}

if test "${SQUASH}" = 'true'; then
    NEW=$(git-notary notes ${OBJECT} ${BASE} ${NAMESPACE} | git-notary squash | git-notary versions | awk '{ print $2 }')
else
    NEW=$(git-notary notes ${OBJECT} ${BASE} ${NAMESPACE} | git-notary versions | tail -1 | awk '{ print $2 }')
fi

LATEST_TAG_ON_BASE=$(git describe --tags --abbrev=0 ${BASE})
OLD=${LATEST_TAG_ON_BASE:-'0.0.0'}
echo "${OLD} -> ${NEW}"

}

# undo [object] [namespace] undo() {

OBJECT=${1:-HEAD}
NAMESPACE=${2:-${GIT_NOTARY_NAMESPACE}}

git notes --ref=${NAMESPACE} remove ${OBJECT}

}

# version version() {

echo "git-notary ${GIT_NOTARY_VERSION}"

}

# help help() {

cat <<EOF

$(version) usage:

git-notary new <major|minor|patch> [object] [namespace]
git-notary undo [object] [namespace]
git-notary delta [--squash] [object] [base] [namespace]

git-notary fetch [remote] [namespace]
git-notary push [remote] [namespace]

git-notary notes [branch] [base] [namespace]
git-notary versions [initial]
git-notary tags [--apply]

EOF }

# notary <command> [args] notary() {

COMMAND=${1}
shift
case ${COMMAND} in
    N|notes)
        notes ${@};;
    V|versions)
        versions ${@};;
    T|tags)
        tags ${@};;
    S|squash)
        squash ${@};;
    Z|squeeze)
        squeeze ${@};;
    n|new)
        new ${@};;
    u|undo)
        undo ${@};;
    d|delta)
        delta ${@};;
    P|push)
        push ${@};;
    f|fetch)
        fetch ${@};;
    fm|fetch-merge|pull)
        pull ${@};;
    M|major)
        new MAJOR ${@};;
    m|minor)
        new MINOR ${@};;
    p|patch)
        new PATCH ${@};;
    v|version|-v|--version)
        version;;
    h|help|-h|--help)
        help;;
    *)
        help
        exit 1
        ;;
esac

}

notary “${@:-}”