summaryrefslogtreecommitdiff
path: root/scripts/git-dist.mk
blob: 2a8b2c26dda0b8aac890688a3ea9f4629dbfe70a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
# Automake support for git-based release and distribution management
#
#
# Copyright © 2009-2010  Roger Leigh <rleigh@debian.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/>.
#
#####################################################################

# 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))"
# Message for distribution commit
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, plus
# ENABLE_RELEASE_GIT=true if the working tree has not already been
# tagged with a release tag.  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 ENABLE_RELEASE_GIT="$(ENABLE_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 ENABLE_DIST_GIT="$(ENABLE_DIST_GIT)" 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