Hello,

I finally have something working to manage git-buildpackage from tags.
The debian/changelog is not updated on the branch.

The procedure is the following:

You on your workstation:
------------------------

1. upstream release a new version
2. merge upstream in your debian packaging branch
3. create a "build request tag"[1]
4. push the tag on a central repository

The post-receive hook on a central git repository:
--------------------------------------------------

1. check for specific tag names
2. launch a build.command[2] in background

My build-deb build.command on a central git repository:
-------------------------------------------------------

1. check tag name
2. "clone -l" in a temporary directory
3. switch to last tagged debian package, if any
4. merge the "build request tag"
5. update debian/changelog[3]
6. run git-buildpackage
7. tag new version
6. push the new tag (as we are on temporary clone)
7. send report by mail
8. clean (done by a trap on EXIT)

TODO:

* Use "git-dch --snapshot" if distribution name contains "experimental"
  For version to be incremented in debian/changelog we need to tag
  snapshots too.
  We could keep only one snapshot tag by removing the previous one.
  We could remove the previous snapshot tag when doing non snapshot
  build (no using experimental in distribution name)

* As above, but for --bpo, --nmu, etc

* Remove debian/changelog from the development branch, will be possible
  if I can close #669171[4]

Thanks.

Footnotes: 
[1]  default to something like "build/<vendor>/<distribution>"

[2]  Included build-deb

[3]  This part require a patch[5] for git-dch to set the distribution
     name based on the tag name

[4]  http://bugs.debian.org/669171

[5]  http://bugs.debian.org/646684

-- 
Daniel Dehennin
Récupérer ma clef GPG:
gpg --keyserver pgp.mit.edu --recv-keys 0x7A6FE2DF

#!/bin/bash

#------------------------------------------------------------------------
# Inspired by the post-receive-email hook
# Copyright (c) 2007 Andy Parkins
#
# build-hook - Run a command after receiving a build request tag
# Copyright (C) 2012  Daniel Dehennin <daniel.dehen...@baby-gnu.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

#------------------------------------------------------------------------
# Usage: command line: build-hook <refname> <oldrev> <newrev>
#
# Usage: hook script: echo <oldrev> <newrev> <refname> | build-hook
#
# This script can be be run from command line or as a hook script.
#
# It check for a build tag and run an external command in background.
#
# The tag must match the build.tag-regex git configuration
#
# Config
# ------
# build.tag-regex
#     Extended regular expression (as in regex(7)) to match build
#     request tags. The value must match the <vendor>/<distribution>
#     part used to determine the distribution name.
#     Default value: 'refs/tags/build/[^/]+/.+'
# build.command
#     Run that command in the background with the following arguments:
#     <refname> <oldrev> <newrev>
#     The standard and error outputs are both redirected to /dev/null.
#     No default value

set -e

die() { echo -e "$@" >&2; exit 1; }
function usage() { perl -lne 'print substr($_, 2) if /^# Usage/../^$/' < "${0}"; exit 1; }

[ "${1}" = "-h" -o "${1}" = "--help" ] && usage

BUILD_CMD=$(git config build.command || /bin/true)
BUILD_TAG_REGEX=$(git config build.tag-regex || echo -n 'refs/tags/build/[^/]+/.+')

function check-build-request() {
    # --- Arguments
    refname="${1}"
    # NB: no quote arround right operand of =~
    if ! [[ "${refname}" =~ ${BUILD_TAG_REGEX} ]]
    then
	# Not a build request
        return 1
    fi
    oldrev=$(git rev-parse "${2}" 2>/dev/null)
    newrev=$(git rev-parse "${3}" 2>/dev/null)

    # --- Interpret
    # 0000->1234 (create)
    # 1234->2345 (update)
    # 2345->0000 (delete)
    if expr "${oldrev}" : '0*$' >/dev/null
    then
        change_type="create"
    else
        if expr "${newrev}" : '0*$' >/dev/null
        then
            change_type="delete"
        else
            change_type="update"
        fi
    fi

    # --- Get the revision types
    newrev_type=$(git cat-file -t "${newrev}" 2> /dev/null || true)
    # oldrev_type=$(git cat-file -t "${oldrev}" 2> /dev/null || true)
    case "${change_type}" in
        create|update)
            rev="${newrev}"
            rev_type="${newrev_type}"
            ;;
        delete)
            return 1
            ;;
    esac

    # The revision type tells us what type the commit is, combined with
    # the location of the ref we can decide between
    #  - working branch
    #  - tracking branch
    #  - unannoted tag
    #  - annotated tag
    # Only take care of tags 
    short_refname="${refname##refs/tags/}"
    case "${rev_type}" in
        commit)
            # un-annotated tag
            refname_type="tag"
            ;;
        tag)
            # annotated tag
            refname_type="annotated tag"
            ;;
        *)
	    # Can not happen?
            return 1
            ;;
    esac

    if [ -n "${BUILD_CMD}" ]
    then
	echo "Spool a build from ${refname_type} '${short_refname}'"
    else
	die "No 'build.command' git configuration defined"
    fi
}

# --- Main loop
# Allow dual mode: run from the command line just like the update hook, or
# if no arguments are given then run as a hook script
if [ -n "${1}" -a -n "${2}" -a -n "${3}" ]; then
    # Check if this is a build request and run the source builder command
    check-build-request $@ && "${BUILD_CMD}" $@ > /dev/null 2>&1 &
else
    while read oldrev newrev refname
    do
        check-build-request "${refname}" "${oldrev}" "${newrev}" || continue
        "${BUILD_CMD}" "${refname}" "${oldrev}" "${newrev}" > /dev/null 2>&1 &
    done
fi
#!/bin/bash

#------------------------------------------------------------------------
# Inspired by the post-receive-email hook
# Copyright (c) 2007 Andy Parkins
#
# build-deb - Run git-buildpackage from a tag name
# Copyright (C) 2012  Daniel Dehennin <daniel.dehen...@baby-gnu.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

#------------------------------------------------------------------------
# Usage: build-source <refname> <oldrev> <newrev>
#
# Run "git buildpackage" on a build request tag.
#
# The tag must match the build.tag-regex git configuration
#
# The <distribution> part of the <refname> will get the slash "/"
# transformed to dashes "-":
#
# - build/debian/unstable -> unstable
# - build/debian/wheezy/proposed/updates -> whezzy-proposed-updates
# - build/debian/wheezy/proposed-updates -> whezzy-proposed-updates
#
# The build is done against the last builded package tag reachable.
#
# Config
# ------
# build.home
#     Base directory where a temporary directory is created in which
#     the build occurs.
#     Default value: '${HOME}/build'
# build.tag-regex
#     Extended regular expression (as in regex(7)) to match build
#     request tags. The value must match the <vendor>/<distribution>
#     part used to determine the distribution name.
#     Default value: 'refs/tags/build/[^/]+/.+'
# build.mail
#     If 'true', send a mail of the log file
#     No default value
# build.mail-from
#     Set the From header of the mail report
#     No default value
# build.mail-to
#     Set the To header of the mail report
#     No default value
# build.mail-cc-tag-author
#     If set to 'true', when 'build.mail-to' is defined send a carbon
#     copy to the author of the tag.
#     No default value
#
# Mandatory dependencies:
# -----------------------
# * bash
# * perl
# * cut
# * git
# * git-buildpackage
# * gbp-config: http://bugs.debian.org/672854#35
# * dpkg-parsechangelog
# * mktemp
#
# Optional dependencies:
# ----------------------
# * mailx or heirloom-mailx if the 'build.mail-from' or
#   'build.mail-cc-tag-author' options are set

set -e

die() { echo -e "$@" >&2; exit 1; }
function usage() {
    if [ -n "$@" ]
    then
        echo -e "Error: $@\n" >&2
    fi
    perl -lne 'print substr($_, 2) if /^# Usage/../^$/' < "${0}"
    exit 1
}

[ "${1}" = "-h" -o "${1}" = "--help" ] && usage

BUILD_CMD='git-buildpackage'
BUILD_TAG_REGEX=$(git config build.tag-regex || echo -n 'refs/tags/build/[^/]+/.+')
BUILD_HOME=$(git config build.home || echo -n "${HOME}/build")

function check-build-request() {
    # --- Arguments
    refname="${1}"
    if ! [[ "${refname}" =~ ${BUILD_TAG_REGEX} ]]
    then
        usage
    fi

    oldrev=$(git rev-parse "${2}" 2>/dev/null)
    newrev=$(git rev-parse "${3}" 2>/dev/null)

    # --- Interpret
    # 0000->1234 (create)
    # 1234->2345 (update)
    # 2345->0000 (delete)
    if expr "${oldrev}" : '0*$' >/dev/null
    then
        change_type="create"
    else
        if expr "$newrev" : '0*$' >/dev/null
        then
            change_type="delete"
        else
            change_type="update"
        fi
    fi

    # --- Get the revision types
    newrev_type=$(git cat-file -t "${newrev}" 2> /dev/null || true)
    oldrev_type=$(git cat-file -t "${oldrev}" 2> /dev/null || true)
    case "${change_type}" in
        create|update)
            rev="${newrev}"
            rev_type="${newrev_type}"
            ;;
        delete)
            die "${0} should not be called when deleting objects:\n\n" \
                "change_type: '${change_type}'\n"                      \
                "refname: '${refname}'\n"                              \
                "oldrev: '${oldrev}'\n"                                \
                "oldrev type: '${oldrev_type}'\n"                      \
                "newrev: '${newrev}'"
            ;;
    esac

    # The revision type tells us what type the commit is, combined with
    # the location of the ref we can decide between
    #  - working branch
    #  - tracking branch
    #  - unannoted tag
    #  - annotated tag
    short_refname="${refname##refs/tags/}"
    case "${rev_type}" in
        commit)
            # un-annotated tag
            refname_type="tag"
            tag_author=$(git cat-file -p "${refname}" | perl -lane 'print join(" ", @F[1..@F-3]) if $F[0] eq "committer"')
            ;;
        tag)
            # annotated tag
            refname_type="annotated tag"
            tag_author=$(git cat-file -p "${refname}" | perl -lane 'print join(" ", @F[1..@F-7]) if $F[0] eq "tagger"')
            ;;
        *)
            # Can not happen?
            die "${0} must be called only for a build request:\n\n" \
                "change_type: '${change_type}'\n"                   \
                "refname: '${refname}'\n"                           \
                "oldrev: '${oldrev}'\n"                             \
                "oldrev type: '${oldrev_type}'\n"                   \
                "newrev: '${newrev}'"                               \
                "newrev type: '${newrev_type}'\n"
            ;;
    esac
    # TODO Get tag message without GPG signature
    # TAG=$(git tag -v ${refname})
    # TAG_MSG=$(echo ${TAG} | tail -n +5)
    author_name=$(echo "${tag_author}" | perl -pe 's/\s<[^>]+>.*//xms')
    author_email=$(echo "${tag_author}" | perl -pe 's/[^<]+<([^>]+)>.*/$1/xms')

    # Do it here to send mail to author
    BUILD_CMD=$(type -p "${BUILD_CMD}")
    test -x "${BUILD_CMD}" \
	|| die "Build command '${BUILD_CMD}' not found or not executable"

    return 0
}

function prepare-clone() {
    unset GIT_DIR
    PROJECT=$(basename $(pwd))
    PROJECT="${PROJECT%.git}"

    test -d "${BUILD_HOME}" || mkdir -p "${BUILD_HOME}" \
	|| die "Can not create directory '${BUILD_HOME}'"

    test -w "${BUILD_HOME}" || die "Build directory '${BUILD_HOME}' is not writable"

    # Require a push url to publish tag
    PUSH_URL=$(git config build.push-url || die "No 'build.push-url' configuration: abort")

    BUILD_ROOT=$(mktemp -d "${BUILD_HOME}/${PROJECT}-XXXXXX")
    BUILD_DIR="${BUILD_ROOT}/${PROJECT}"

    cat >&2 <<EOF

┌──────────────────────────────────────────────────────────────────────────────┐
│ Prepare new clone                                                            │
└──────────────────────────────────────────────────────────────────────────────┘

EOF
    git clone -l $(pwd) "${BUILD_DIR}" >&2

}

function deb-get-last-pkg() {
    local refname="${1}"

    cat >&2 <<EOF

┌──────────────────────────────────────────────────────────────────────────────┐
│ Get last debian package                                                      │
└──────────────────────────────────────────────────────────────────────────────┘

EOF

    git checkout -b tmp-XXXXXX "${refname}"
    local deb_tag_fmt=$(gbp-config git-buildpackage debian-tag | cut -d':' -f2-)
    local deb_tag_prefix="${deb_tag_fmt%\%*}"
    local last_pkg=$(git describe --abbrev=0 --match "${deb_tag_prefix}"\*)
    if [ -z "${last_pkg}" ]
    then
        echo "Unable to get last package, use ${refname}" >&2
        last_pkg="${refname}"
    else
        echo "Use last package from '${last_pkg}'" >&2
    fi
    echo "${last_pkg}"
}

function deb-get-distribution() {
    local short_refname="${1}"
    # Get the disribution from tag name (build/<vendor>/<base name>/<release>)
    # For example: build/debian/wheezy/proposed/updates

    cat >&2 <<EOF

┌──────────────────────────────────────────────────────────────────────────────┐
│ Get distribution                                                             │
└──────────────────────────────────────────────────────────────────────────────┘

EOF

    # strip build/
    local dist="${short_refname#*/}"
    # strip vendor/
    dist="${dist#*/}"
    # replace / with -
    dist="${dist//\//-}"

    echo "Set distribution to '${dist}'" >&2
    echo "${dist}"
}

function main() {
    LOG_FILE=$(mktemp --tmpdir "build-deb-XXXXXX.log")
    exec 2> "${LOG_FILE}"

    # --- Arguments
    refname="${1}"
    oldrev=$(git rev-parse "${2}" 2>/dev/null)
    newrev=$(git rev-parse "${3}" 2>/dev/null)

    cat >&2 <<EOF
┌──────────────────────────────────────────────────────────────────────────────┐
│ Build source package                                                         │
└──────────────────────────────────────────────────────────────────────────────┘

Build request from: ${tag_author}
Tag: ${short_refname}

EOF

    prepare-clone

    cd "${BUILD_DIR}"

    local last_pkg=$(deb-get-last-pkg "${refname}")
    local build_branch=$(gbp-config git-buildpackage debian-branch | cut -d':' -f2-)
    test -z "${build_branch}" && die "Unable to get build branch"

    local dist=$(deb-get-distribution "${short_refname}")
    test -z "${dist}" && die "Unable to get distribution name"

    cat >&2 <<EOF

┌──────────────────────────────────────────────────────────────────────────────┐
│ Merge new package                                                            │
└──────────────────────────────────────────────────────────────────────────────┘

EOF
    git checkout -b "${build_branch}" "${last_pkg}" 1>&2 \
        || die "Unable to switch to build branch '${build_branch}' from '${last_pkg}'"

    git merge --no-edit --no-ff -m "New upstream release by ${author_name}" "${refname}" >&2

    cat >&2 <<EOF

┌──────────────────────────────────────────────────────────────────────────────┐
│ Update debian/changelog                                                      │
└──────────────────────────────────────────────────────────────────────────────┘

EOF
    git-dch -a debian/ >&2 || die "Unable to update debian/changelog"

    git add "debian/changelog" >&2 || die "Unable to add debian/changelog to git stagging area"

    local changes=$(dpkg-parsechangelog)
    local deb_version=$(echo $changes | awk '/^Version/ {print $2}')
    git commit -a -m "${author_name}: New version ${deb_version} for ${dist}." >&2

    echo -e "\nChanges:" >&2
    echo -e "=======:\n" >&2
    echo "${changes}" >&2

    cat >&2 <<EOF

┌──────────────────────────────────────────────────────────────────────────────┐
│ git-buildpackage build                                                       │
└──────────────────────────────────────────────────────────────────────────────┘

EOF

    git buildpackage >&2 || die "Unable to build debian source package"

    cat >&2 <<EOF

┌──────────────────────────────────────────────────────────────────────────────┐
│ git-buildpackage tag new version                                             │
└──────────────────────────────────────────────────────────────────────────────┘

EOF

    git buildpackage --git-tag-only >&2 || die "Unable to create new tag"

    local new_version=$(git describe --tags --exact-match || die "No release tag created")

    cat >&2 <<EOF

┌──────────────────────────────────────────────────────────────────────────────┐
│ Publish new tag                                                              │
└──────────────────────────────────────────────────────────────────────────────┘

EOF
    echo "Push '${new_version} to '${PUSH_URL}${PROJECT}'" >&2
    git push "${PUSH_URL}${PROJECT}" "${new_version}" >&2 || die "Unable to push new version tag"

    NEW_VERSION=$(git ls-remote --tags "${PUSH_URL}${PROJECT}" "${new_version}")
}

function clean() {
    local mail_from
    local mail_to
    local mail_cc

    cat >&2 <<EOF

┌──────────────────────────────────────────────────────────────────────────────┐
│ Cleaning                                                                     │
└──────────────────────────────────────────────────────────────────────────────┘

EOF
    # We may fail to early
    if [ -n "${LOG_FILE}" ]
    then

        # Block new build request of last was not successful
        if [ -n "${NEW_VERSION}" ]
        then
            git tag -d "${short_refname}" >&2
            git push "${PUSH_URL}${PROJECT}" ":${refname}" >&2
        fi
        if [ -f "${LOG_FILE}" ] && git config build.mail
        then
	    mail_from=$(git config build.mail-from || /bin/true)
	    test -n "${mail_from}" && mail_from="-r ${mail_from}"

	    mail_to=$(git config build.mail-to || /bin/true)
	    if [ -n "${mail_to}" ]
	    then
		if [[ $(git config build.mail-cc-tag-author) =~ ^t|T ]]
		then
		    mail_cc="-S autocc=${author_email}"
		fi
	    else
		mail_to="${author_email}"
	    fi

	    mail -s "[${PROJECT}] Build debian source package" \
		"${mail_from}" "${mail_cc}" "${mail_to}" < "${LOG_FILE}" \
		|| /bin/true # Be sure to delete ${BUILD_ROOT}
        fi
        cd "${HOME}"
        rm -f "${LOG_FILE}"
        rm -rf "${BUILD_ROOT}"
    fi
}

trap clean EXIT

# --- Main loop
if [ -n "$1" -a -n "$2" -a -n "$3" ]; then
    # Check if this is a build request and run it
    check-build-request $@ && main $@
else
    usage "Invalid argument"
fi

Attachment: pgp4UdtN9Ov9T.pgp
Description: PGP signature

_______________________________________________
vcs-pkg-discuss mailing list
vcs-pkg-discuss@lists.alioth.debian.org
http://lists.alioth.debian.org/cgi-bin/mailman/listinfo/vcs-pkg-discuss

Reply via email to