]> granicus.if.org Git - handbrake/commitdiff
build: changes to version numbering and build process
authorJohn Stebbins <jstebbins.hb@gmail.com>
Fri, 28 Aug 2015 18:16:56 +0000 (11:16 -0700)
committerJohn Stebbins <jstebbins.hb@gmail.com>
Wed, 23 Sep 2015 18:45:42 +0000 (11:45 -0700)
Use date/time for snapshot version numbers, YYYYMMDDHHMMSS-hash-branch.
Add --snapshot configure option to force snapshot builds.
repo-info.sh and tag-release.sh improvements.

make/configure.py
pkg/linux/module.defs
pkg/linux/module.rules
scripts/repo-info.sh
scripts/tag-release.sh

index ecb2816b06410df9b320a0837ee7e0a4c31cd8b8..e606f40c1bed911a4757d2d88a8da2be9026d9fb 100644 (file)
@@ -16,6 +16,7 @@ import re
 import subprocess
 import sys
 import time
+from datetime import datetime, timedelta
 
 from optparse import OptionGroup
 from optparse import OptionGroup
@@ -693,12 +694,13 @@ class RepoProbe( ShellProbe ):
 
         self.url       = 'git://nowhere.com/project/unknown'
         self.tag       = ''
+        self.tag_hash  = 'deadbeaf'
         self.branch    = 'unknown'
         self.remote    = 'unknown'
         self.rev       = 0
         self.hash      = 'deadbeaf'
         self.shorthash = 'deadbea'
-        self.date      = '0000-00-00 00:00:00 -0000'
+        self.date      = datetime(1, 1, 1)
         self.official  = 0
         self.type      = 'developer'
 
@@ -714,6 +716,8 @@ class RepoProbe( ShellProbe ):
                 self.url = value
             elif name == 'TAG':
                 self.tag = value
+            elif name == 'TAG_HASH':
+                self.tag_hash = value
             elif name == 'BRANCH':
                 self.branch = value
             elif name == 'REMOTE':
@@ -721,7 +725,20 @@ class RepoProbe( ShellProbe ):
             elif name == 'REV':
                 self.rev = int( value )
             elif name == 'DATE':
-                self.date = value
+                self.date = datetime.strptime(value[0:19], "%Y-%m-%d %H:%M:%S")
+
+                # strptime can't handle UTC offset
+                m = re.match( '^([-+]?[0-9]{2})([0-9]{2})$', value[20:])
+                (hh, mn) = m.groups()
+                utc_off_hour   = int(hh)
+                utc_off_minute = int(mn)
+                if utc_off_hour >= 0:
+                    utc_off = utc_off_hour * 60 + utc_off_minute
+                else:
+                    utc_off = utc_off_hour * 60 - utc_off_minute
+                delta = timedelta(minutes=utc_off)
+                self.date = self.date - delta
+
             elif name == 'HASH':
                 self.hash = value
                 self.shorthash = value[:7]
@@ -733,7 +750,7 @@ class RepoProbe( ShellProbe ):
 
         if self.url == official_url:
             self.official = 1
-            if self.branch == '' and self.rev == 0:
+            if not options.snapshot and self.hash == self.tag_hash:
                 self.type = 'release'
             else:
                 self.type = 'developer'
@@ -788,6 +805,9 @@ class Project( Action ):
         self.vmajor = 0
         self.vminor = 0
         self.vpoint = 0
+        self.spoint = 0
+        self.suffix = ''
+        self.special = ''
 
     def _action( self ):
         ## add architecture to URL only for Mac
@@ -796,33 +816,52 @@ class Project( Action ):
         else:
             url_arch = ''
 
+        suffix = ''
         if repo.tag != '':
-            m = re.match( '^([0-9]+)\.([0-9]+)\.([0-9]+)$', repo.tag )
+            m = re.match( '^([0-9]+)\.([0-9]+)\.([0-9]+)-?(.*)?$', repo.tag )
             if not m:
                 cfg.errln( 'Invalid repo tag format %s\n', repo.tag )
                 sys.exit( 1 )
-            (vmajor, vminor, vpoint) = m.groups()
+            (vmajor, vminor, vpoint, suffix) = m.groups()
             self.vmajor = int(vmajor)
             self.vminor = int(vminor)
             self.vpoint = int(vpoint)
+            self.suffix = suffix
 
-        if repo.type == 'release':
-            self.version = '%d.%d.%d' % (self.vmajor,self.vminor,self.vpoint)
-            url_ctype = ''
-            url_ntype = 'stable'
-            self.build = time.strftime('%Y%m%d') + '00'
-            self.title = '%s %s (%s)' % (self.name,self.version,self.build)
-        else:
+        if repo.type != 'release' or options.snapshot:
+            self.version = repo.date.strftime("%Y%m%d%H%M%S")
+            self.version += '-%s' % (repo.shorthash)
             if repo.branch != '':
-                self.version = '%d.%d.%d-%d-%s-%s' % (self.vmajor, self.vminor,
-                    self.vpoint, repo.rev, repo.shorthash, repo.branch)
-            else:
-                self.version = '%d.%d.%d-%d-%s' % (self.vmajor, self.vminor,
-                    self.vpoint, repo.rev, repo.shorthash)
+                self.version += '-%s' % (repo.branch)
+
+            self.debversion = repo.date.strftime("%Y%m%d%H%M%S")
+            self.debversion += '-%s' % (repo.shorthash)
+            if repo.branch != '':
+                self.debversion += '-%s' % (repo.branch)
+
             url_ctype = '_unstable'
             url_ntype = 'unstable'
             self.build = time.strftime('%Y%m%d') + '01'
             self.title = '%s %s (%s)' % (self.name,self.version,self.build)
+        else:
+            m = re.match('^([a-zA-Z]+)\.([0-9]+)$', suffix)
+            if not m:
+                # Regular release
+                self.version = '%d.%d.%d' % (self.vmajor,self.vminor,self.vpoint)
+                self.debversion = '%d.%d.%d' % (self.vmajor, self.vminor, self.vpoint)
+                url_ctype = ''
+                url_ntype = 'stable'
+            else:
+                (special, spoint,) = m.groups()
+                self.special = special
+                self.spoint = int(spoint)
+                self.version = '%d.%d.%d-%s.%d' % (self.vmajor,self.vminor,self.vpoint, self.special, self.spoint)
+                self.debversion = '%d.%d.%d~%s.%d' % (self.vmajor, self.vminor, self.vpoint, self.special, self.spoint)
+                url_ctype = '_unstable'
+                url_ntype = 'unstable'
+
+            self.build = time.strftime('%Y%m%d') + '00'
+            self.title = '%s %s (%s)' % (self.name,self.version,self.build)
 
         self.url_appcast = 'https://handbrake.fr/appcast%s%s.xml' % (url_ctype,url_arch)
         self.url_appnote = 'https://handbrake.fr/appcast/%s.html' % (url_ntype)
@@ -1278,6 +1317,13 @@ def createCLI():
     for select in SelectTool.selects:
         select.cli_add_option( grp )
     cli.add_option_group( grp )
+
+    ## add build options
+    grp = OptionGroup( cli, 'Build Options' )
+    grp.add_option( '--snapshot', default=False, action='store_true',
+                    help='Force a snapshot build' )
+    cli.add_option_group( grp )
+
     return cli
 
 ###############################################################################
@@ -1663,7 +1709,9 @@ int main ()
     doc.add( 'HB.version.major',  project.vmajor )
     doc.add( 'HB.version.minor',  project.vminor )
     doc.add( 'HB.version.point',  project.vpoint )
+    doc.add( 'HB.version.suffix', project.suffix )
     doc.add( 'HB.version',        project.version )
+    doc.add( 'HB.debversion',     project.debversion )
     doc.add( 'HB.version.hex',    '%04x%02x%02x%08x' % (project.vmajor,project.vminor,project.vpoint,repo.rev) )
 
     doc.add( 'HB.build', project.build )
@@ -1677,7 +1725,7 @@ int main ()
     doc.add( 'HB.repo.remote',    repo.remote )
     doc.add( 'HB.repo.type',      repo.type )
     doc.add( 'HB.repo.official',  repo.official )
-    doc.add( 'HB.repo.date',      repo.date )
+    doc.add( 'HB.repo.date',      repo.date.strftime("%Y-%m-%d %H:%M:%S") )
 
     doc.addBlank()
     doc.add( 'HOST.spec',    host.spec )
index 3b1a81f8ed253f92906a0eca39ccbbef21b9fbcc..2eea7feb9269d02fdd91102cdee58c2b808a8b60 100644 (file)
@@ -28,15 +28,15 @@ PKG.rpm.src.tar.bz2 = $(STAGE.out.src/)rpm/$(PKG.rpm.basename).tar.bz2
 STAGE.out.rpm.src/ = $(STAGE.out.src/)rpm/
 
 PKG.debian  = $(PKG.in/)linux/debian
-PKG.cli.deb = $(PKG.out/)$(HB.name)-$(HB.version)-Ubuntu_CLI_$(BUILD.machine).deb
-PKG.gui.deb = $(PKG.out/)$(HB.name)-$(HB.version)-Ubuntu_GUI_$(BUILD.machine).deb
-PKG.deb.basename = $(HB.name.lower)-$(HB.version)
-PKG.src.deb.tar = $(HB.name.lower)_$(HB.version).tar.gz
+PKG.cli.deb = $(PKG.out/)$(HB.name)-$(HB.debversion)-Ubuntu_CLI_$(BUILD.machine).deb
+PKG.gui.deb = $(PKG.out/)$(HB.name)-$(HB.debversion)-Ubuntu_GUI_$(BUILD.machine).deb
+PKG.deb.basename = $(HB.name.lower)-$(HB.debversion)
+PKG.src.deb.tar = $(HB.name.lower)_$(HB.debversion).tar.gz
 PKG.src.deb.stamp = $(STAGE.out.src/).debsrc.stamp
-PKG.src.deb = $(PKG.out/)$(HB.name.lower)_$(HB.version).deb
+PKG.src.deb = $(PKG.out/)$(HB.name.lower)_$(HB.debversion).deb
 
-PKG.cli.tmp.deb = $(PKG.out/)$(HB.name.lower)-cli_$(HB.version)_$(PKG.deb.machine).deb
-PKG.gui.tmp.deb = $(PKG.out/)$(HB.name.lower)-gtk_$(HB.version)_$(PKG.deb.machine).deb
+PKG.cli.tmp.deb = $(PKG.out/)$(HB.name.lower)-cli_$(HB.debversion)_$(PKG.deb.machine).deb
+PKG.gui.tmp.deb = $(PKG.out/)$(HB.name.lower)-gtk_$(HB.debversion)_$(PKG.deb.machine).deb
 
 PKG.native.rpm.stamp = $(RPM.out/).rpm.stamp
 PKG.rpm.stamp = $(PKG.out/).rpm.stamp
index e0d823f1758af1b5f76038f7f7898a65eda8691d..a08db25172ff5c6c58acd0a37c8c8c48992f439c 100644 (file)
@@ -65,7 +65,7 @@ $(PKG.gui.tmp.deb): GNUmakefile
        fakeroot $(MAKE) -C $(SRC/) -f debian/rules clean
        $(MAKE) BUILDDIR=$(PWD)/$(BUILD) CONFIGURE=configure -C $(SRC/) -f debian/rules build
        echo $(PKG.out/)
-       fakeroot $(MAKE) FORCEVERSION="-- -v$(HB.version)" BUILDDIR=$(PWD)/$(BUILD) CONFIGURE=configure PKGDESTDIR=$(PWD)/$(PKG.out/) -C $(SRC/) -f debian/rules binary
+       fakeroot $(MAKE) FORCEVERSION="-- -v$(HB.debversion)" BUILDDIR=$(PWD)/$(BUILD) CONFIGURE=configure PKGDESTDIR=$(PWD)/$(PKG.out/) -C $(SRC/) -f debian/rules binary
 
 $(PKG.gui.deb): | $(dir $(PKG.gui.deb))
 $(PKG.gui.deb): $(PKG.gui.tmp.deb)
@@ -79,8 +79,8 @@ $(PKG.cli.deb): $(PKG.gui.tmp.deb)
 # Debian source package rules
 #
 pkg.push.src.deb:: $(PKG.src.deb.stamp)
-       (cd $(STAGE.out.src/)vivid && dput handbrake-git-snapshots $(HB.name.lower)_$(HB.version)ppa1~vivid1_source.changes )
-       (cd $(STAGE.out.src/)trusty && dput handbrake-git-snapshots $(HB.name.lower)_$(HB.version)ppa1~trusty1_source.changes )
+       (cd $(STAGE.out.src/)vivid && dput handbrake-git-snapshots $(HB.name.lower)_$(HB.debversion)ppa1~vivid1_source.changes )
+       (cd $(STAGE.out.src/)trusty && dput handbrake-git-snapshots $(HB.name.lower)_$(HB.debversion)ppa1~trusty1_source.changes )
 
 $(PKG.src.deb.stamp): GNUmakefile
        -$(RM.exe) -rf $(STAGE.out.src/)
@@ -92,7 +92,7 @@ $(PKG.src.deb.stamp): GNUmakefile
        cp -a $(PWD)/$(PKG.debian) $(STAGE.out.src/)vivid/$(PKG.deb.basename)
        $(CP.exe) $(STAGE.out.src/)vivid/$(PKG.deb.basename)/debian/control.vivid $(STAGE.out.src/)vivid/$(PKG.deb.basename)/debian/control
        $(CP.exe) $(STAGE.out.src/)vivid/$(PKG.deb.basename)/debian/rules.vivid $(STAGE.out.src/)vivid/$(PKG.deb.basename)/debian/rules
-       echo "$(HB.name.lower) ($(HB.version)ppa1~vivid1) vivid; urgency=low" > $(STAGE.out.src/)vivid/$(PKG.deb.basename)/debian/changelog
+       echo "$(HB.name.lower) ($(HB.debversion)-1ppa1~vivid1) vivid; urgency=low" > $(STAGE.out.src/)vivid/$(PKG.deb.basename)/debian/changelog
        echo "  * Snapshot" >> $(STAGE.out.src/)vivid/$(PKG.deb.basename)/debian/changelog
        echo "    - See timeline at http://trac.handbrake.fr/timeline" >> $(STAGE.out.src/)vivid/$(PKG.deb.basename)/debian/changelog
        echo "" >> $(STAGE.out.src/)vivid/$(PKG.deb.basename)/debian/changelog
@@ -106,7 +106,7 @@ $(PKG.src.deb.stamp): GNUmakefile
        cp -a $(PWD)/$(PKG.debian) $(STAGE.out.src/)trusty/$(PKG.deb.basename)
        $(CP.exe) $(STAGE.out.src/)trusty/$(PKG.deb.basename)/debian/control.trusty $(STAGE.out.src/)trusty/$(PKG.deb.basename)/debian/control
        $(CP.exe) $(STAGE.out.src/)trusty/$(PKG.deb.basename)/debian/rules.trusty $(STAGE.out.src/)trusty/$(PKG.deb.basename)/debian/rules
-       echo "$(HB.name.lower) ($(HB.version)ppa1~trusty1) trusty; urgency=low" > $(STAGE.out.src/)trusty/$(PKG.deb.basename)/debian/changelog
+       echo "$(HB.name.lower) ($(HB.debversion)-1ppa1~trusty1) trusty; urgency=low" > $(STAGE.out.src/)trusty/$(PKG.deb.basename)/debian/changelog
        echo "  * Snapshot" >> $(STAGE.out.src/)trusty/$(PKG.deb.basename)/debian/changelog
        echo "    - See timeline at http://trac.handbrake.fr/timeline" >> $(STAGE.out.src/)trusty/$(PKG.deb.basename)/debian/changelog
        echo "" >> $(STAGE.out.src/)trusty/$(PKG.deb.basename)/debian/changelog
index 0e402535d555f510959c86280c5c55fdf243c642..4e8795344c6c389b5cab8629274b7863d39e73a4 100755 (executable)
@@ -2,66 +2,78 @@
 #
 # Retrieves git repository info for directory ${1} using command ${2}
 
-# Args
-REPO_DIR='.'
-if [[ ${1} ]]; then
-    REPO_DIR=${1}
-fi
-GIT_EXE='git'
-if [[ ${2} ]]; then
-    GIT_EXE=${2}
-fi
+function repo_info()
+{
+    local repo_dir git_exe commit upstream err
 
-# Switch to working directory
-if ! cd ${REPO_DIR} 2>/dev/null; then
-    echo "Invalid directory ${REPO_DIR}." 1>&2
-    exit 1
-fi
+    # Process args
+    repo_dir='.'
+    if [[ ${1} ]]; then
+        repo_dir=${1}
+    fi
+    git_exe='git'
+    if [[ ${2} ]]; then
+        git_exe=${2}
+    fi
+
+    # Switch to working directory
+    if ! cd ${repo_dir} 2>/dev/null; then
+        echo "Invalid directory ${repo_dir}." 1>&2
+        return 1
+    fi
 
-# Check whether we have git
-if ! hash ${GIT_EXE} 2>/dev/null; then
-    echo "Command '${GIT_EXE}' not found." 1>&2
-    exit 1
-fi
+    # Check whether we have git
+    if ! hash ${git_exe} 2>/dev/null; then
+        echo "Command '${git_exe}' not found." 1>&2
+        return 1
+    fi
 
-# Check if there is a valid git repo here
-HASH=$(${GIT_EXE} rev-parse HEAD)
-ERR=$?
-if [[ ${ERR} -ne 0 ]]; then
-    echo "Not a valid repository." 1>&2
-    exit ${ERR}
-elif [[ -z ${HASH} ]]; then
-    echo "Not a valid repository." 1>&2
-    exit 1
-fi
+    # Check if there is a valid git repo here
+    HASH=$(${git_exe} rev-parse HEAD)
+    SHORTHASH=$(${git_exe} rev-parse --short HEAD)
+    err=$?
+    if [[ ${err} -ne 0 ]]; then
+        echo "Not a valid repository." 1>&2
+        return ${err}
+    elif [[ -z ${HASH} ]]; then
+        echo "Not a valid repository." 1>&2
+        return 1
+    fi
 
-# Retrieve info
-URL=$(${GIT_EXE} config remote.origin.url)
-TAG=$(${GIT_EXE} describe --tags --abbrev=0)
-if [[ ${TAG} ]]; then
-    REV=$(${GIT_EXE} rev-list ${TAG}.. --count)
-else
-    TAG=$(${GIT_EXE} describe --tags $(${GIT_EXE} rev-list --tags --max-count=1))
+    # Retrieve info
+    URL=$(${git_exe} config remote.origin.url)
+
+    # check if an annotated tag is reachable from HEAD
+    TAG=$(${git_exe} describe --tags --abbrev=0 --exact-match --match \[0-9\]\*.\[0-9\]\*.\[0-9\]\* HEAD 2> /dev/null)
     if [[ ${TAG} ]]; then
-        REV=$(${GIT_EXE} rev-list $(${GIT_EXE} merge-base ${TAG} HEAD).. --count)
+        # if TAG is a release tag and HASH == TAG_HASH, this is release code
+        TAG_HASH=$(${git_exe} rev-list ${TAG} --max-count=1)
+        REV=$(${git_exe} rev-list $(${git_exe} merge-base ${TAG} HEAD).. --count)
     else
-        REV=$(${GIT_EXE} rev-list HEAD --count)
+        REV=$(${git_exe} rev-list HEAD --count)
+    fi
+
+    BRANCH=$(${git_exe} symbolic-ref -q --short HEAD)
+    REMOTE="${URL}"
+    upstream=$(${git_exe} config branch.${BRANCH}.remote)
+    if [[ ${upstream} ]]; then
+        REMOTE="${upstream}"
     fi
-fi
-BRANCH=$(${GIT_EXE} symbolic-ref -q --short HEAD)
-REMOTE="${URL}"
-UPSTREAM=$(${GIT_EXE} config branch.${BRANCH}.remote)
-if [[ ${UPSTREAM} ]]; then
-    REMOTE="${UPSTREAM}"
-fi
-DATE=$(${GIT_EXE} log -1 --format="format:%ai")
+    DATE=$(${git_exe} log -1 --format="format:%ci")
+
+    # Output
+    # Only write tag and rev if they exist.
+    echo "URL=${URL}"
+    echo "HASH=${HASH}"
+    echo "SHORTHASH=${SHORTHASH}"
+    if [[ ${TAG} ]]; then echo "TAG=${TAG}"; fi
+    if [[ ${TAG_HASH} ]]; then echo "TAG_HASH=${TAG_HASH}"; fi
+    if [[ ${REV} ]]; then echo "REV=${REV}"; fi
+    echo "BRANCH=${BRANCH}"
+    echo "REMOTE=${REMOTE}"
+    echo "DATE=${DATE}"
+
+    return 0
+}
 
-# Output
-# Only write tag and rev if they exist.
-echo "URL=${URL}"
-echo "HASH=${HASH}"
-if [[ ${TAG} ]]; then echo "TAG=${TAG}"; fi
-if [[ ${REV} ]]; then echo "REV=${REV}"; fi
-echo "BRANCH=${BRANCH}"
-echo "REMOTE=${REMOTE}"
-echo "DATE=${DATE}"
+repo_info "$@"
index 66e33e30806cb830ceb130b4f817bccd18a86b56..9f7d488f63d324a9f01f147d16bc7a26b9bd0446 100755 (executable)
 #! /bin/bash
 #
-# Usage: tag-release.sh <release-ver> [<commit>]
+# Usage: tag-release.sh <release-ver> [<ref>]
 #
-# Creates a new branch and tag for the release
-# Optionally, the release can be based off a specific git commit.
+# Creates a new bugfix branch and release tag for a release.
+# Optionally, the release can be based off a specific git ref.
+# Default is HEAD of the current branch.
+#
+# The script does a lot of error checking, but boils down to
+# the following actions.
+#
+# For major/minor releases (X.Y.0) from master branch:
+#   git checkout -b X.Y.x
+#   git tag -a X.Y.Z -m "X.Y.Z" HEAD
+# and optionally:
+#   git push -u origin X.Y.x
+#   git push origin refs/tags/X.Y.Z
+#
+# For bugfix releases from associated branch (X.Y.x):
+#   git tag -a X.Y.Z -m "X.Y.Z" HEAD
+# and optionally:
+#   git push origin refs/tags/X.Y.Z
 #
 
-TAG=${1}
-COMMIT=${2}
-
-if [ "x${TAG}" == "x" ]; then
-    echo "Missing release tag (e.g. 0.10.0)"
-fi
-
-if [ "x${COMMIT}" == "x" ]; then
-    echo "Creating release tag ${TAG} and branch ${TAG}-dev from HEAD, proceed?"
-else
-    echo "Creating release tag ${TAG} and branch ${TAG}-dev from ${COMMIT}, proceed?"
-fi
-read proceed
-if [[ ( "x${proceed}" != "xy" ) && ( "x${proceed}" != "xY" ) ]] ; then
-    echo "Aborting..."
-    exit 0
-fi
-
-if [ "x${COMMIT}" != "x" ]; then
-    # create release branch from specific commit
-    git checkout "${COMMIT}" -b "${TAG}-dev"
-    ERR=$?
-else
-    # create release branch from head of current branch
-    git checkout -b "${TAG}-dev"
-    ERR=$?
-fi
-if [ ${ERR} -ne 0 ]; then
-    echo "Failed to create branch ${TAG}-dev"
-    exit ${ERR}
-fi
-
-# create tag
-git tag -a "${TAG}" -m "Release ${TAG}" HEAD
-ERR=$?
-if [ ${ERR} -ne 0 ]; then
-    echo "Failed to create tag ${TAG}"
-    # cleanup... remove the branch that was created
-    git branch -d "${TAG}-dev"
-    exit ${ERR}
-fi
-
-# checkout tag in preparation for building release
-# this should put you in a "detached HEAD" state
-git checkout "${TAG}"
-ERR=$?
-if [ ${ERR} -ne 0 ]; then
-    echo "Failed to checkout tag ${TAG}"
-    # cleanup... remove the branch that was created
-    git branch -d "${TAG}-dev"
-    exit ${ERR}
-fi
-
-remote=$(git config remote.origin.url)
-echo
-echo "Do you wish to push this release branch and tag to $remote? (y/N)"
-echo "You may want to do this manually after creating and verifying release."
-echo "e.g."
-echo "    git push -u origin ${TAG}-dev"
-echo "    git push origin ${TAG}"
-read proceed
-if [[ ( "x${proceed}" == "xy" ) || ( "x${proceed}" == "xY" ) ]] ; then
-    git push -u origin "${TAG}-dev"
+GIT_EXE='git'
+
+function validate_repo()
+{
+    local HASH err AHEAD BEHIND proceed
+
+    # Check whether we have git
+    if ! hash ${GIT_EXE} 2>/dev/null; then
+        echo "Command '${GIT_EXE}' not found." 1>&2
+        return 1
+    fi
+
+    # Check if there is a valid git repo here
+    HASH=$(${GIT_EXE} rev-parse HEAD)
+    err=$?
+    if [[ ${err} -ne 0 ]]; then
+        echo "Not a valid repository." 1>&2
+        return ${err}
+    elif [[ -z ${HASH} ]]; then
+        echo "Not a valid repository." 1>&2
+        return 1
+    fi
+
+    if [[ -n "$(${GIT_EXE} status --porcelain)" ]]; then
+        echo "There are uncommitted changes.  Aborting." 1>&2
+        return 1
+    fi
+
+    echo "Fetching repo data..."
+    ${GIT_EXE} fetch
+    err=$?
+    if [[ ${err} -ne 0 ]]; then
+        echo "Failed to fetch repo data." 1>&2
+        return ${err}
+    fi
+    AHEAD=$(${GIT_EXE} rev-list @{u}..HEAD --count)
+    BEHIND=$(${GIT_EXE} rev-list HEAD..@{u} --count)
+    if [[ ${AHEAD} -ne 0 ]]; then
+        echo "There are unpushed changes. Continue anyway? (y/N)"
+        read proceed
+        if [[ ( "x${proceed}" != "xy" ) && ( "x${proceed}" != "xY" ) ]] ; then
+            echo "Aborting..."
+            return 1
+        fi
+    fi
+    if [[ ${BEHIND} -ne 0 ]]; then
+        echo "There are unmerged upstream changes. Continue anyway? (y/N)"
+        read proceed
+        if [[ ( "x${proceed}" != "xy" ) && ( "x${proceed}" != "xY" ) ]] ; then
+            echo "Aborting..."
+            return 1
+        fi
+    fi
+}
+
+function tag_release()
+{
+    local TAG REF COMMIT BRANCH proceed new_branch ERR HASH
+
+    TAG=${1}
+    REF=${2}
+
+    if [ "x${TAG}" == "x" ]; then
+        echo "Missing release tag (e.g. 0.10.0)"
+    fi
+
+    # bugfix branch name
+    BRANCH=${TAG%.[0-9]*}.x
+
+    if [ "x${REF}" == "x" ]; then
+        echo "Creating release tag ${TAG} and branch ${BRANCH} from HEAD, proceed? (y/N)"
+        # retrive full hash of HEAD
+        COMMIT=$(${GIT_EXE} rev-list HEAD --max-count=1)
+    else
+        echo "Creating release tag ${TAG} and branch ${BRANCH} from ${REF}, proceed? (y/N)"
+        # retrieve full hash from ref or short hash
+        COMMIT=$(${GIT_EXE} rev-list ${REF} --max-count=1)
+    fi
+    read proceed
+    if [[ ( "x${proceed}" != "xy" ) && ( "x${proceed}" != "xY" ) ]] ; then
+        echo "Aborting..."
+        return 0
+    fi
+
+    # check if the remote branch already exists
+    ${GIT_EXE} rev-parse --quiet --verify origin/${BRANCH} > /dev/null
+    if [ $? -ne 0 ]; then
+        # remote branch does not exist
+        new_branch=1
+        # does the branch already exist locally?
+        ${GIT_EXE} rev-parse --quiet --verify ${BRANCH} > /dev/null
+        if [ $? -ne 0 ]; then
+            # local branch does not exist
+            # create bugfix branch from commit
+            ${GIT_EXE} checkout "${COMMIT}" -b "${BRANCH}"
+            ERR=$?
+            if [ ${ERR} -ne 0 ]; then
+                echo "Failed to create branch ${BRANCH}"
+                return ${ERR}
+            fi
+        else
+            # local branch already exists
+            # When the branch already exists, make sure it is being used!
+            current_branch=$(${GIT_EXE} rev-parse --abbrev-ref HEAD)
+            if [ "$current_branch" != "${BRANCH}" ]; then
+                echo "You did not checkout the correct branch ${BRANCH} for tag ${TAG}"
+                return 1
+            fi
+        fi
+    else
+        new_branch=0
+        # When the branch already exists, make sure it is being used!
+        current_branch=$(${GIT_EXE} rev-parse --abbrev-ref HEAD)
+        if [ "$current_branch" != "${BRANCH}" ]; then
+            echo "You did not checkout the correct branch ${BRANCH} for tag ${TAG}"
+            return 1
+        fi
+    fi
+
+    # at this point we should be at the head of the tracking branch
+    # for this release.  Make certain that HEAD matches COMMIT
+    HASH=$(${GIT_EXE} rev-list HEAD --max-count=1)
+    if [ ${HASH} != ${COMMIT} ]; then
+        echo "Commit specified does not match current branch HEAD"
+        return 1
+    fi
+
+    # create tag
+    ${GIT_EXE} tag -a "${TAG}" -m "${TAG}" HEAD
     ERR=$?
     if [ ${ERR} -ne 0 ]; then
-        echo "Failed to push branch ${TAG}-dev to remote"
-        exit ${ERR}
+        echo "Failed to create tag ${TAG}"
+        # cleanup... remove the branch that was created
+        ${GIT_EXE} branch -d "${BRANCH}"
+        return ${ERR}
     fi
-    git push origin "${TAG}"
+
+    # checkout tag in preparation for building release
+    # this should put you in a "detached HEAD" state
+    ${GIT_EXE} checkout "${TAG}"
     ERR=$?
     if [ ${ERR} -ne 0 ]; then
-        echo "Failed to push tag ${TAG}-dev to remote"
-        exit ${ERR}
+        echo "Failed to checkout tag ${TAG}"
+        # cleanup... remove the branch that was created
+        ${GIT_EXE} branch -d "${BRANCH}"
+        return ${ERR}
+    fi
+
+    remote=$(${GIT_EXE} config remote.origin.url)
+    echo
+    echo "Do you wish to push this release branch and tag to $remote? (y/N)"
+    echo "You may want to do this manually after creating and verifying release."
+    echo "e.g."
+    echo "    git push -u origin ${BRANCH}"
+    echo "    git push origin refs/tags/${TAG}"
+    read proceed
+    if [[ ( "x${proceed}" == "xy" ) || ( "x${proceed}" == "xY" ) ]] ; then
+        if [ $new_branch .eq 1 ]; then
+            ${GIT_EXE} push -u origin "${BRANCH}"
+            ERR=$?
+            if [ ${ERR} -ne 0 ]; then
+                echo "Failed to push branch ${BRANCH} to remote"
+                return ${ERR}
+            fi
+        fi
+        ${GIT_EXE} push origin refs/tags/"${TAG}"
+        ERR=$?
+        if [ ${ERR} -ne 0 ]; then
+            echo "Failed to push tag ${BRANCH} to remote"
+            return ${ERR}
+        fi
+    else
+        echo "Branch and tag are local, changes not pushed to remote!"
+    fi
+
+}
+
+function main()
+{
+    if validate_repo; then
+        tag_release "$@"
+    else
+        return $?
     fi
-else
-    echo "Branch and tag are local, changes not pushed to remote!"
-fi
+}
 
+main "$@"