# Automake support for git-based release and distribution management # # # Copyright © 2009-2010 Roger Leigh # # 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 # . # ##################################################################### # Include this file in your top-level Makefile.am using # include $(top_srcdir)/scripts/git-dist.mk # (for example) # Note that this script uses GNU make-specific functions, and so run # automake with -Wno-portability or add this to the automake options # in configure.ac. # Customise using the following variables. See below for instructions # on what each variable does. Note that these could be overridden in # the Makefile including this Makefile fragment, so this file doesn't # need editing directly. # Check for untracked files in working tree GIT_CHECK_UNTRACKED=true # GPG sign release tags GIT_RELEASE_TAG_SIGN=true # Naming scheme for release tags. Note: must include $(VERSION). GIT_RELEASE_TAG_NAME=release/$(PACKAGE)-$(VERSION) # Message for release tags. GIT_RELEASE_TAG_MESSAGE="Release of $(PACKAGE)-$(VERSION)" # GPG sign distribution tags GIT_DIST_TAG_SIGN=true # Branch to place distributed release on GIT_DIST_BRANCH="$(basename distribution-$(VERSION))" GIT_DIST_COMMIT_MESSAGE="Distribution of $(PACKAGE) version $(VERSION)" # Naming scheme for distribution tags. Note: must include $(VERSION). GIT_DIST_TAG_NAME=distribution/$(PACKAGE)-$(VERSION) # Message for distribution tags. GIT_DIST_TAG_MESSAGE="Distribution of $(PACKAGE)-$(VERSION)" # Release to distribute. GIT_DIST_ROOT=$(distdir) # Check that the working tree and index are clean prior to making any # changes. If dirty, then the changes may be unreproducible and not # match what was expected. For example, the distributed files may not # match those actually committed or may not even be under version # control. # # Project customisation: # Checking of untracked files may be disabled by setting # GIT_CHECK_UNTRACKED to false. check-git: @cd "$(abs_top_srcdir)"; \ if [ ! -d .git ]; then \ echo "$@: Not a git repository" 1>&2; \ exit 1; \ fi; \ git diff-index --quiet HEAD; \ case $$? in \ 0) \ if [ "$(GIT_CHECK_UNTRACKED)" == "true" ]; then \ exclude_options=""; \ git ls-files --others --exclude-standard --exclude="$(GIT_DIST_ROOT)" --error-unmatch . >/dev/null 2>&1; \ case $$? in \ 0) \ echo "$@: Untracked files present in working tree"; \ exit 1; \ ;; \ 1) \ : \ ;; \ *) \ echo "$@: Error checking working tree"; \ exit 1; \ ;; \ esac; \ fi; \ ;; \ 1) \ echo "$@: Uncommitted changes in working tree"; \ exit 1; \ ;; \ *) \ echo "$@: Error checking git index"; \ exit 1; \ ;; \ esac; # Make a release. # # The current working tree is tagged as a new release. If the release # tag already exists then the operation will do nothing if the tag # matches the current working tree, or else it will abort with an # error. If the repository has been accidentally tagged previously, # then remove the tag with "git tag -d TAG" before releasing. # # NOTE: Set ENABLE_RELEASE_GIT=true when running make. This is a # safety check to avoid accidental damage to the git repository. # # NOTE: Running release-git independently of dist-git is NOT # RECOMMENDED. The distdir rule can update files in the working tree # (for example, gettext translations in po/), so running "make # distdir" prior to tagging the release will ensure the tagged release # will not differ from the distributed release. # # Project customisation: # The tag will be signed by default; set GIT_RELEASE_TAG_SIGN to # alter. The tag will be named using GIT_RELEASE_TAG_NAME with the # GIT_RELEASE_TAG_MESSAGE specifying an appropriate message for the # tag. release-git: @cd "$(abs_top_srcdir)"; \ if [ ! -d .git ]; then \ echo "$@: Not a git repository" 1>&2; \ exit 1; \ fi; \ if git show-ref --tags -q $(GIT_RELEASE_TAG_NAME); then \ echo "$@: git release tag $(GIT_RELEASE_TAG_NAME) already exists; not releasing" 1>&2; \ exit 0; \ fi; \ if [ "$(ENABLE_RELEASE_GIT)" != "true" ]; then \ echo "$@: ENABLE_RELEASE_GIT not true; not releasing"; \ exit 1; \ fi; \ $(MAKE) $(AM_MAKEFLAGS) check-git; \ echo "$@: releasing $(PACKAGE)-$(VERSION)"; \ RELEASE_TAG_OPTS=""; \ if [ "$(GIT_RELEASE_TAG_SIGN)" = "true" ]; then \ RELEASE_TAG_OPTS="$$TAG_OPTS -s"; \ fi; \ git tag -m $(GIT_RELEASE_TAG_MESSAGE) $$RELEASE_TAG_OPTS "$(GIT_RELEASE_TAG_NAME)" HEAD || exit 1; \ echo "$(PACKAGE) $(VERSION) release tagged as $(GIT_RELEASE_TAG_NAME)"; # Make a distribution of a release. # # A distribution is created and committed onto the specified branch. # The commit is then tagged. The distribution commit will have the # release commit and the previous distribution (if any) as its # parents. Thus distribution releases appear to git as merges (with # the exception of the initial release). # # NOTE: Set ENABLE_DIST_GIT=true when running make. This is a safety # check to avoid accidental damage to the git repository. # # Project customisation: # GIT_DIST_COMMIT_MESSAGE specifies the commit message for the commit, # and GIT_DIST_BRANCH specifies the branch to add the commit to. # The tag will be signed by default; set GIT_DIST_TAG_SIGN to # alter. The tag will be named using GIT_DIST_TAG_NAME with the # GIT_DIST_TAG_MESSAGE specifying an appropriate message for the # tag. dist-git: distdir $(MAKE) $(AM_MAKEFLAGS) release-git; \ RELEASE_COMMIT="$$(git rev-parse $(GIT_RELEASE_TAG_NAME)^{})"; \ HEAD_COMMIT="$$(git rev-parse HEAD)"; \ if [ "$$RELEASE_COMMIT" != "$$HEAD_COMMIT" ]; then \ echo "$@: Working tree is not at $(GIT_RELEASE_TAG_NAME)^{} $$RELEASE_COMMIT; not distributing"; \ exit 1; \ fi; \ $(MAKE) $(AM_MAKEFLAGS) check-git; \ $(MAKE) $(AM_MAKEFLAGS) dist-git-generic GIT_DIST_ROOT="$(abs_top_builddir)/$(distdir)"; \ $(am__remove_distdir) # Make a distribution of an arbitrary release. # The same as dist-git, but this allows addition of any distribution # rather than just the release in the current working tree. This rule # is intended for allowing retrospective addition of a project's # entire release history (driven by a shell script), for example. # See below for an example of how to do this. # # GIT_DIST_ROOT must be set to specify the release to distribute and # VERSION must match the release version. GIT_DIST_BRANCH may also # require setting if not using the default. GIT_RELEASE_TAG_NAME must # be set to the tag name of the existing release. dist-git-generic: $(GIT_DIST_ROOT) @cd "$(abs_top_srcdir)"; \ if [ ! -d .git ]; then \ echo "$@: Not a git repository" 1>&2; \ exit 1; \ fi; \ if git show-ref --tags -q $(GIT_DIST_TAG_NAME); then \ echo "$@: git distribution tag $(GIT_DIST_TAG_NAME) already exists; not distributing" 1>&2; \ exit 1; \ fi; \ if [ "$(ENABLE_DIST_GIT)" != "true" ]; then \ echo "$@: ENABLE_DIST_GIT not true; not distributing"; \ exit 0; \ fi; \ echo "$@: distributing $(PACKAGE)-$(VERSION) on git branch $(GIT_DIST_BRANCH)"; \ DIST_INDEX="$(abs_top_builddir)/.git-dist-index"; \ DIST_TREE="$(GIT_DIST_ROOT)"; \ rm -f "$$DIST_INDEX"; \ GIT_INDEX_FILE="$$DIST_INDEX" GIT_WORK_TREE="$$DIST_TREE" git add -A || exit 1; \ TREE="$$(GIT_INDEX_FILE="$$DIST_INDEX" git write-tree)"; \ rm -f "$$DIST_INDEX"; \ [ -n "$$TREE" ] || exit 1; \ RELEASE_COMMIT="$$(git rev-parse $(GIT_RELEASE_TAG_NAME)^{})"; \ COMMIT_OPTS="-p $$RELEASE_COMMIT"; \ DIST_PARENT="$$(git show-ref --heads -s refs/heads/$(GIT_DIST_BRANCH))"; \ if [ -n "$$DIST_PARENT" ]; then \ COMMIT_OPTS="$$COMMIT_OPTS -p $$DIST_PARENT"; \ fi; \ COMMIT="$$(echo $(GIT_DIST_COMMIT_MESSAGE) | git commit-tree "$$TREE" $$COMMIT_OPTS)"; \ [ -n "$$COMMIT" ] || exit 1; \ git update-ref "refs/heads/$(GIT_DIST_BRANCH)" "$$COMMIT" "$$DIST_PARENT" || exit 1;\ if [ -n "$$DIST_PARENT" ]; then \ NEWROOT="(root-commit) "; \ fi; \ echo "[$(GIT_DIST_BRANCH) $${NEWROOT}$$COMMIT] $(GIT_DIST_COMMIT_MESSAGE)"; \ DIST_TAG_OPTS=""; \ if [ "$(GIT_DIST_TAG_SIGN)" = "true" ]; then \ DIST_TAG_OPTS="$$TAG_OPTS -s"; \ fi; \ git tag -m $(GIT_DIST_TAG_MESSAGE) $$DIST_TAG_OPTS "$(GIT_DIST_TAG_NAME)" "$$COMMIT" || exit 1; \ echo "$@: $(PACKAGE) $(VERSION) distribution tagged as $(GIT_DIST_TAG_NAME)"; .PHONY: check-git release-git dist-git dist-git-generic # Example: How to retrospectively insert the complete distribution # history for a project. Note: GIT_RELEASE_TAG_NAME must match the # pattern used to tag all previous releases since this requires tags # for all releases. # # #!/bin/sh # # set -e # # # Clean up any existing distribution branches and tags which could # # interfere with addition of a complete clean distribution history. # git tag -l | grep distribution | while read tag; do # git tag -d $tag # done; # git branch -l | grep distribution | while read branch; do # git branch -D $branch # done; # # # Read an ordered list of versions from release-versions and get # # distribution for each version from the given path and distribute # # in git # while read version; do # make dist-git-generic GIT_DIST_ROOT="/path/to/unpacked/releases/$package-$version" VERSION="$version" ENABLE_DIST_GIT=true # done < release-versions # Example: How to check that the added distributions are correctly # representing the content of the old distributed releases following # import as in the above example. # # #!/bin/sh # # set -e # # # For each version in the list of releases, check out and unpack # # that version, and then compare it with the original. # while read version; do # git checkout distribution/package-$version # rm -rf /tmp/package-$version # mkdir /tmp/package-$version # git archive HEAD | tar -x -C /tmp/$package-$version # diff -urN /tmp/$package-$version "/path/to/unpacked/releases/$package-$version" | lsdiff # rm -rf /tmp/package-$version # done < release-versions