summaryrefslogtreecommitdiffstats
path: root/util/getrevision.sh
blob: 03d0b1ee1107cab069b09d2fa2b87cd71c693a6d (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
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
#!/bin/sh
#
# This file is part of the flashrom project.
#
# Copyright (C) 2005 coresystems GmbH <stepan@coresystems.de>
# Copyright (C) 2009,2010 Carl-Daniel Hailfinger
# Copyright (C) 2010 Chromium OS Authors
# Copyright (C) 2013-2016 Stefan Tauner
#
# 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 2 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, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
#

EXIT_SUCCESS=0
EXIT_FAILURE=1

# Make sure we don't get translated output
export LC_ALL=C
# nor local times or dates
export TZ=UTC0

# List of important upstream branches...
upstream_branches="stable staging"
upstream_url="https://flashrom.org/git/flashrom.git"
upstream_patterns="github\.com.flashrom/flashrom(\.git)?|flashrom\.org.git/flashrom(\.git)?"

upcache_prefix="refs/flashrom_org/"
# Generate upcache_refs
for b in $upstream_branches ; do
	upcache_refs="$upcache_refs ${upcache_prefix}$b"
done

# We need to update our upstream information sometimes so that we can create detailed version information.
# To that end the code below fetches parts of the upstream repository via https.
# This takes about one second or less under normal circumstances.
#
# It can be called manually, but is usually called via
#  - the Makefile (implicitly via revision()) when there is no upstream information in any existing remote
forced_update() {
	local rev_remote_refs
	for ref in $upcache_refs ; do
		rev_remote_refs="$rev_remote_refs +${ref##*/}:$ref"
	done
	git fetch -q "$upstream_url" --tags $rev_remote_refs && echo "Success."
}

update() {
	offline=$(git config flashrom.offline-builds 2>/dev/null)
	if [ -z "$offline" ]; then
		echo "To produce useful version information the build process needs access to the commit
history from an upstream repository. If no git remote is pointing to one we
can store the necessary information out of sight and update it on every build.
To enable this functionality and fetch the upstream commits from $upstream_url
please execute 'git config flashrom.offline-builds false' or add one of the
upstream repositories as git remote to rely on that information.
However, if you want to work completely offline and generate possibly meaningless
version strings then disable it with 'git config flashrom.offline-builds true'
You can force updating the local commit cache with '$0 --forced-update'">&2
		return 1
	elif [ "x$offline" = "xfalse" ]; then
		echo "Fetching commit history from upstream repository $upstream_url
To disable any network activity execute 'git config flashrom.offline-builds true'.">&2
		forced_update >/dev/null
	else
		echo "Fetching commit history from upstream is disabled - version strings might be misleading.
To ensure proper version strings and allow network access run 'git config flashrom.offline-builds false'.">&2
	fi
	return 0
}

# Helper functions
# First argument is the path to inspect (usually optional; w/o it the whole repository will be considered)
git_has_local_changes() {
	git update-index -q --refresh >/dev/null
	! git diff-index --quiet HEAD -- "$1"
}

git_last_commit() {
	# git rev-parse --short HEAD would suffice if repository as a whole is of interest (no $1)
	git log --pretty=format:"%h" -1 -- "$1"
}

git_is_file_tracked() {
	git ls-files --error-unmatch -- "$1" >/dev/null 2>&1
}

is_file_tracked() {
	git_is_file_tracked "$1"
}

# Tries to find a remote source for the changes committed locally.
# This includes the URL of the remote repository including the last commit and a suitable branch name.
# Takes one optional argument: the path to inspect
git_url() {
	last_commit=$(git_last_commit "$1")
	# get all remote branches containing the last commit (excluding origin/HEAD and git-svn branches/tags)
	branches=$(git branch -r --contains $last_commit | sed '/\//!d;/.*->.*/d;s/[\t ]*//')
	if [ -z "$branches" ] ; then
		echo "No remote branch contains a suitable commit">&2
		return
	fi

	# find "nearest" branch
	local mindiff=9000
	local target=
	for branch in $branches ; do
		curdiff=$(git rev-list --count $last_commit..$branch)
		if [ $curdiff -ge $mindiff ] ; then
			continue
		fi
		mindiff=$curdiff
		target=$branch
	done

	echo "$(git ls-remote --exit-code --get-url ${target%/*}) ${target#*/}"
}

# Returns a string indicating where others can get the current source code (excluding uncommitted changes)
# Takes one optional argument: the path to inspect
scm_url() {
	local url=

	if git_is_file_tracked "$1" ; then
		url="$(git_url "$1")"
	else
		return ${EXIT_FAILURE}
	fi

	echo "${url}"
}

# Retrieve timestamp since last modification. If the sources are pristine,
# then the timestamp will match that of the SCM's most recent modification
# date.
timestamp() {
	local t

	# date syntaxes are manifold:
	# gnu		date [-d input]... [+FORMAT]
	# netbsd	date [-ajnu] [-d date] [-r seconds] [+format] [[[[[[CC]yy]mm]dd]HH]MM[.SS]]
	# freebsd	date [-jnu]  [-d dst] [-r seconds] [-f fmt date | [[[[[cc]yy]mm]dd]HH]MM[.ss]] [+format] [...]
	# dragonflybsd	date [-jnu]  [-d dst] [-r seconds] [-f fmt date | [[[[[cc]yy]mm]dd]HH]MM[.ss]] [+format] [...]
	# openbsd	date [-aju]  [-d dst] [-r seconds] [+format] [[[[[[cc]yy]mm]dd]HH]MM[.SS]] [...]
	if git_is_file_tracked "$2" ; then
		# are there local changes?
		if git_has_local_changes "$2" ; then
			t=$(date -u "${1}")
		else
			# No local changes, get date of the last commit
			case $(uname) in
			# Most BSD dates do not support parsing date values from user input with -d but all of
			# them support parsing epoch seconds with -r. Thanks to git we can easily use that:
			NetBSD|OpenBSD|DragonFly|FreeBSD)
				t=$(date -u -r "$(git log --pretty=format:%ct -1 -- $2)"  "$1" 2>/dev/null);;
			*)
				t=$(date -d "$(git log --pretty=format:%cD -1 -- $2)" -u "$1" 2>/dev/null);;
			esac
		fi
	else
		t=$(date -u "$1")
	fi

	if [ -z "$t" ]; then
		echo "Warning: Could not determine timestamp." 2>/dev/null
	fi
	echo "${t}"
}

tag() {
	local t=

	if git_is_file_tracked "$1" ; then
		local sha=$(git_last_commit "$1")
		t=$(git describe --abbrev=0 "$sha")
	fi
	if [ -z "$t" ]; then
		t="unknown" # default to unknown
	fi
	echo "${t}"
}

find_upremote() {
	# Try to find upstream's remote name
	for remote in $(git remote) ; do
		local url=$(git ls-remote --get-url $remote)
		if echo "$url" | grep -q -E "$upstream_patterns" ; then
			echo "$remote"
			return
		fi
	done
}

revision() {
	local sha=$(git_last_commit "$1" 2>/dev/null)
	# No git no fun
	if [ -z "$sha" ]; then
		echo "unknown"
		return
	fi

	local r="$sha"
	if git_has_local_changes "$1" ; then
		r="$r-dirty"
	fi

	# sha + possibly dirty info is not exactly verbose, therefore the code below tries to use tags and
	# branches from the upstream repos to derive a more previse version string.
	# To that end we try to use the existing remotes first.
	# If the upstream repos (and its mirrors) are not available as remotes, use a shadow copy instead.

	local up_refs
	local up_remote=$(find_upremote)
	if [ -n "$up_remote" ]; then
		for b in $upstream_branches ; do
			up_refs="$up_refs ${up_remote}/${b}"
		done
	else
		update || { echo "offline" ; return ; }
		up_refs=$upcache_refs
	fi

	# Find nearest commit contained in this branch that is also in any of the up_refs, i.e. the branch point
	# of the current branch. This might be the latest commit if it's in any of the upstream branches.
	local merge_point=$(git merge-base ${sha} ${up_refs})
	local upstream_branch
	if [ -z "$merge_point" ]; then
		echo "$sha"
		return
	fi

	# If the current commit is reachable from any remote branch, append the branch name and its
	# distance to the nearest earlier tag to that tag name itself (tag-distance-branch).
	# If multiple branches are reachable then we use the newest one (by commit date).
	# If none is reachable we use the nearest tag and ? for distances and remote branch name.

	local cnt_upstream_branch2sha=$(git rev-list --count "${merge_point}..${sha}" 2>/dev/null)

	local lasttag=$(git describe --abbrev=0 "$merge_point" 2>/dev/null)
	if [ -z "$lasttag" ]; then
		echo "Could not find tag reachable from merge point!">&2
		echo "$sha"
		return
	fi

	local cnt_tag2upstream_branch
	for ref in $up_refs ; do
		if git merge-base --is-ancestor ${merge_point} ${ref}; then
			upstream_branch=${ref##*/} # remove everything till last /
			cnt_tag2upstream_branch=$(git rev-list --count "${lasttag}..${merge_point}" 2>/dev/null)
			break
		fi
	done

	if [ "$cnt_upstream_branch2sha" -gt 0 ]; then
		r="$cnt_upstream_branch2sha-$r"
	fi
	if [ "$cnt_tag2upstream_branch" -gt 0 ]; then
		if [ -n "$upstream_branch" ]; then
			r="$upstream_branch-$r"
		fi
		r="$cnt_tag2upstream_branch-$r"
	fi
	r="$lasttag-$r"

	echo "${r}"
}

is_tracked() {
	is_file_tracked "$1"
}

show_help() {
	echo "Usage:
	${0} <command> [path]

Commands
    -h or --help
        this message
    -c or --check
        test if path is under version control at all
    -T or --tag
        returns the name of the last release/tag
    -r or --revision
        return unique revision information including the last tag and an indicator for uncommitted changes
    -u or --url
        URL associated with the latest commit
    -d or --date
        date of most recent modification
    -t or --timestamp
        timestamp of most recent modification
    -U or --update
        update local shadow copy of upstream commits if need be and offline builds are not enforced
          --forced-update
        force updating the local shadow copy of upstream commits
"
	return
}

check_action() {
	if [ -n "$action" ]; then
		echo "Error: Multiple actions given.">&2
		exit ${EXIT_FAILURE}
	fi
}

main() {
	local query_path=
	local action=

	# Argument parser loop
	while [ $# -gt 0 ];
	do
		case ${1} in
		-h|--help)
			action=show_help;
			shift;;
		-T|--tag)
			check_action $1
			action=tag
			shift;;
		-r|--revision)
			check_action $1
			action=revision
			shift;;
		-u|--url)
			check_action $1
			action=scm_url
			shift;;
		-d|--date)
			check_action $1
			action="timestamp +%Y-%m-%d" # refrain from suffixing 'Z' to indicate it's UTC
			shift;;
		-t|--timestamp)
			check_action $1
			action="timestamp +%Y-%m-%dT%H:%M:%SZ" # There is only one valid time format! ISO 8601
			shift;;
		-U|--update)
			check_action $1
			action=update
			shift;;
		--forced-update)
			check_action $1
			action=forced_update
			shift;;
		-c|--check)
			check_action $1
			action=is_tracked
			shift;;
		-*)
			show_help;
			echo "Error: Invalid option: ${1}"
			exit ${EXIT_FAILURE};;
		*)
			if [ -z "$query_path" ] ; then
				if [ ! -e "$1" ] ; then
					echo "Error: Path \"${1}\" does not exist.">&2
					exit ${EXIT_FAILURE}
				fi
				query_path=$1
			else
				echo "Warning: Ignoring overabundant parameter: \"${1}\"">&2
			fi
			shift;;
		esac;
	done

	# default to current directory (usually equals the whole repository)
	if [ -z "$query_path" ] ; then
		query_path=.
	fi
	if [ -z "$action" ] ; then
		show_help
		echo "Error: No actions specified"
		exit ${EXIT_FAILURE}
	fi

	$action "$query_path"
}

main $@
OpenPOWER on IntegriCloud