Convert flashrom to git

Message ID 20161121001932.23674-1-stefan.tauner@alumni.tuwien.ac.at
State New
Headers show

Commit Message

Stefan Tauner Nov. 21, 2016, 12:19 a.m.
- Drop support for Subversion in the getrevision script and Makefile.
 - Add .gitignore file
 - Restore modification dates of the exported files from the SCM.
 - Stop exporting SCM log dumps to CHANGELOG. This makes no sense.
 - Do not export the pre-"compiled" manpage. It can be generated like
   anything else from the code dump when we export the respective variable.
   The latter is added with this change.
 - Add some initial client-side git hooks
   * When committing check for obvious stuff you never want anyway:
     - white space errors
     - duplicate sign-offs
   * When pushing to the upstream repository check mandatory rules:
      - existing signoffs and acks in all new commits
      - no deletions or creation of branches
      - do not rewrite history of the precious branches, even if forced
 - Change version string of flashrom as follows.
   Previously, we included the last stable version according to a hard-
   coded string in the Makefile and appended the subversion revision number.

   With this patch the version string includes the last reachable git tag,
   number of commits since said tag in the upstream branches (if any),
   the name of said upstream branch, number of commits since that branch
   (if any), and the shortened git hash.
   In case there are uncommitted changes a "-dirty" indicator is also added.
   The case of unknown versions is explicitly handled in getrevision instead
   of simply appending "-unknown" to a hardcoded release number.

   The version information is either taken from an existing git remote
   pointing to an upstream repository (or a known mirror), or if that
   is not available - with the user's consent - a shadow copy is fetched
   from the upstream repo that is updated on every build (usually takes
   less than a second).

In the following some examples of the version string changes are shown.
Basically we print the distance to the last known upstream tag, which
comprises any upstream commits since that tag as well as local commits on
top of that. Additionally we handle upstream's stable and staging branches
specially.

Old output:
flashrom v0.9.7-r1716 on Linux 3.8.0-6-generic (x86_64)

New output variants:

Build of the 0.9.99 release without any changes:
flashrom 0.9.99-e4f6643 on Linux 3.13.0-76-generic (x86_64)

5 commits since last tag in upstream's stable branch:
flashrom 0.9.99-5-stable-e4f6643-dirty on Linux 3.13.0-76-generic (x86_64)

3 commits since last tag in upstream's staging branch and
4 local commits on top of that:
flashrom 0.9.99-3-staging-4-e4f6643 on Linux 3.13.0-76-generic (x86_64)

3 commits since last tag in upstream's staging branch and
4 local commits on top of that, and some local uncommitted changes too:
flashrom 0.9.99-3-staging-4-e4f6643-dirty on Linux 3.13.0-76-generic (x86_64)

3 commits since the last tag in an unrelated upstream branch
(e.g., a stable release *branch* such as 0.9.99) or local branch:
flashrom 0.9.99-3-e4f6643 on Linux 3.13.0-76-generic (x86_64)

No tags reachable from current commit (generic git fallback):
flashrom d95935a version on Linux 3.13.0-76-generic (x86_64)

Not in a repository:
flashrom unknown version on Linux 3.13.0-76-generic (x86_64)

Signed-off-by: Stefan Tauner <stefan.tauner@alumni.tuwien.ac.at>
Acked-by: Stefan Tauner <stefan.tauner@alumni.tuwien.ac.at>
---
 .gitignore                    |  14 +++
 Makefile                      |  45 +++++---
 flashrom.c                    |   2 +-
 util/getrevision.sh           | 257 +++++++++++++++++++++++++++---------------
 util/git-hooks/applypatch-msg |  15 +++
 util/git-hooks/commit-msg     |  15 +++
 util/git-hooks/install.sh     |  17 +++
 util/git-hooks/pre-applypatch |  12 ++
 util/git-hooks/pre-commit     |   9 ++
 util/git-hooks/pre-push       |  74 ++++++++++++
 util/git-hooks/wrapper.sh     |  10 ++
 11 files changed, 360 insertions(+), 110 deletions(-)
 create mode 100644 .gitignore
 create mode 100755 util/git-hooks/applypatch-msg
 create mode 100755 util/git-hooks/commit-msg
 create mode 100755 util/git-hooks/install.sh
 create mode 100755 util/git-hooks/pre-applypatch
 create mode 100755 util/git-hooks/pre-commit
 create mode 100755 util/git-hooks/pre-push
 create mode 100755 util/git-hooks/wrapper.sh

This is my current state of the infrastructure "needed" for flashrom to
convert to git. Most of it is concerned with the version string generation
and I had to jump through a number of hoops to get this far. The exact
version strings are not final (e.g., tags and thus the first part of the
string will get a 'v' prepended).

We also need to find a proper way to integrate this into Gerrit... but
it's a start *ahem* ;)

You can test this code and the respective repo by cloning from here:
https://flashrom.org/git/flashrom.git
However, this is not final and *will* change before we finalize the
migration. Currently the genuine source of flashrom's source is still
the subversion repo!

Comments

Nico Huber Nov. 22, 2016, 10:57 p.m. | #1
Hi Stefan,

could you please split this up? I started to review but I guess the
patch will have gone through some revisions before I'm done reading.

On 21.11.2016 01:19, Stefan Tauner wrote:
>  - Drop support for Subversion in the getrevision script and Makefile.
>  - Add .gitignore file
>  - Restore modification dates of the exported files from the SCM.
>  - Stop exporting SCM log dumps to CHANGELOG. This makes no sense.
>  - Do not export the pre-"compiled" manpage. It can be generated like
>    anything else from the code dump when we export the respective variable.
>    The latter is added with this change.
>  - Add some initial client-side git hooks
>    * When committing check for obvious stuff you never want anyway:
>      - white space errors
>      - duplicate sign-offs
>    * When pushing to the upstream repository check mandatory rules:
>       - existing signoffs and acks in all new commits
>       - no deletions or creation of branches
>       - do not rewrite history of the precious branches, even if forced
>  - Change version string of flashrom as follows.
>    Previously, we included the last stable version according to a hard-
>    coded string in the Makefile and appended the subversion revision number.
> 
>    With this patch the version string includes the last reachable git tag,
>    number of commits since said tag in the upstream branches (if any),
>    the name of said upstream branch, number of commits since that branch
>    (if any), and the shortened git hash.
>    In case there are uncommitted changes a "-dirty" indicator is also added.
>    The case of unknown versions is explicitly handled in getrevision instead
>    of simply appending "-unknown" to a hardcoded release number.
> 
>    The version information is either taken from an existing git remote
>    pointing to an upstream repository (or a known mirror), or if that
>    is not available - with the user's consent - a shadow copy is fetched
>    from the upstream repo that is updated on every build (usually takes
>    less than a second).
> 
> In the following some examples of the version string changes are shown.
> Basically we print the distance to the last known upstream tag, which
> comprises any upstream commits since that tag as well as local commits on
> top of that. Additionally we handle upstream's stable and staging branches
> specially.
> 
> Old output:
> flashrom v0.9.7-r1716 on Linux 3.8.0-6-generic (x86_64)
> 
> New output variants:
> 
> Build of the 0.9.99 release without any changes:
> flashrom 0.9.99-e4f6643 on Linux 3.13.0-76-generic (x86_64)

Well, that's redundant. Why doesn't it just say 0.9.99? If somebody
would forge the tag locally, he could also just override getrevision.sh.

Nico

> 
> 5 commits since last tag in upstream's stable branch:
> flashrom 0.9.99-5-stable-e4f6643-dirty on Linux 3.13.0-76-generic (x86_64)
> 
> 3 commits since last tag in upstream's staging branch and
> 4 local commits on top of that:
> flashrom 0.9.99-3-staging-4-e4f6643 on Linux 3.13.0-76-generic (x86_64)
> 
> 3 commits since last tag in upstream's staging branch and
> 4 local commits on top of that, and some local uncommitted changes too:
> flashrom 0.9.99-3-staging-4-e4f6643-dirty on Linux 3.13.0-76-generic (x86_64)
> 
> 3 commits since the last tag in an unrelated upstream branch
> (e.g., a stable release *branch* such as 0.9.99) or local branch:
> flashrom 0.9.99-3-e4f6643 on Linux 3.13.0-76-generic (x86_64)
> 
> No tags reachable from current commit (generic git fallback):
> flashrom d95935a version on Linux 3.13.0-76-generic (x86_64)
> 
> Not in a repository:
> flashrom unknown version on Linux 3.13.0-76-generic (x86_64)
> 
> Signed-off-by: Stefan Tauner <stefan.tauner@alumni.tuwien.ac.at>
> Acked-by: Stefan Tauner <stefan.tauner@alumni.tuwien.ac.at>
Stefan Tauner Nov. 22, 2016, 11:24 p.m. | #2
On Tue, 22 Nov 2016 23:57:05 +0100
Nico Huber <nico.h@gmx.de> wrote:

> Hi Stefan,
> 
> could you please split this up? I started to review but I guess the
> patch will have gone through some revisions before I'm done reading.

Hi,

I had it split up but since pretty much all of it is somehow
interdependent I ended up combining it into one patch because it was
rather futile. Do you have ideas where/how to split it?

BTW it is work in progress and I have already fixed some bugs available
in the staging branch. If we can we may wanna continue the review on
gerrit... I need to sort some things out with Patrick first though.

> > 
> > Build of the 0.9.99 release without any changes:
> > flashrom 0.9.99-e4f6643 on Linux 3.13.0-76-generic (x86_64)  
> 
> Well, that's redundant. Why doesn't it just say 0.9.99? If somebody
> would forge the tag locally, he could also just override getrevision.sh.

It is redundant in most cases, yes. However, with the hash you can
check the revision out even if you don't have the tags for some reason.
Also, it brings consistency since we simply always add the hash to the
version string. After all, the hash is *the* identifying attribute of a
commit in git. And we did include the svn revision number in releases as
well. The superfluous nature alone doesn't convince me.

Patch

diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..c478a67
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,14 @@ 
+*.d
+*.o
+/.features
+/.dependencies
+/.libdeps
+/build_details.txt
+/flashrom
+/flashrom.exe
+/flashrom.8
+
+/util/ich_descriptors_tool/ich_descriptors_tool
+/util/ich_descriptors_tool/ich_descriptors_tool.exe
+/util/ich_descriptors_tool/.dep
+/util/ich_descriptors_tool/.obj
diff --git a/Makefile b/Makefile
index 4ebde1e..c880764 100644
--- a/Makefile
+++ b/Makefile
@@ -526,18 +526,20 @@  LIB_OBJS = layout.o flashrom.o udelay.o programmer.o helpers.o
 
 CLI_OBJS = cli_classic.o cli_output.o cli_common.o print.o
 
-# Set the flashrom version string from the highest revision number of the checked out flashrom files.
+# Set the flashrom version string from the repository metadata (cf. util/getrevision.sh).
 # Note to packagers: Any tree exported with "make export" or "make tarball"
-# will not require subversion. The downloadable snapshots are already exported.
-SVNVERSION := $(shell ./util/getrevision.sh -u 2>/dev/null )
-
-RELEASE := 0.9.9
-VERSION := $(RELEASE)-$(SVNVERSION)
-RELEASENAME ?= $(VERSION)
+# will not require git. The downloadable snapshots are already exported.
+VERSION := $(shell ./util/getrevision.sh --revision)
+# VERSION equals "offline" if online access is required but the respective git config variable is not set yet.
+ifeq ($(VERSION),offline)
+  $(error Aborting.)
+endif
+SCMDEF := -D'FLASHROM_VERSION="$(VERSION)"'
 
-SVNDEF := -D'FLASHROM_VERSION="$(VERSION)"'
+# No spaces in release names unless set explicitly
+RELEASENAME ?= $(shell echo "$(VERSION)" | sed -e 's/ /_/')
 
-# Inform user if there is no meaningful version string. If there is version information from a VCS print
+# Inform user about the version string used. If there is no version information from a VCS then print
 # something anyway because $(info...) will print a line break in any case which would look suspicious.
 # The && between the echos is a workaround for old versions of GNU make that issue the error "unterminated
 # variable reference" if a semicolon is used instead.
@@ -1024,7 +1026,7 @@  libflashrom.a: $(LIBFLASHROM_OBJS)
 TAROPTIONS = $(shell LC_ALL=C tar --version|grep -q GNU && echo "--owner=root --group=root")
 
 %.o: %.c .features
-	$(CC) -MMD $(CFLAGS) $(CPPFLAGS) $(FLASHROM_CFLAGS) $(FEATURE_CFLAGS) $(SVNDEF) -o $@ -c $<
+	$(CC) -MMD $(CFLAGS) $(CPPFLAGS) $(FLASHROM_CFLAGS) $(FEATURE_CFLAGS) $(SCMDEF) -o $@ -c $<
 
 # Make sure to add all names of generated binaries here.
 # This includes all frontends and libflashrom.
@@ -1344,12 +1346,16 @@  endif
 	@$(DIFF) -q .features.tmp .features >/dev/null 2>&1 && rm .features.tmp || mv .features.tmp .features
 	@rm -f .featuretest.c .featuretest$(EXEC_SUFFIX)
 
+
+$(shell ./util/git-hooks/install.sh) # Automatically install if need be
+
 $(PROGRAM).8.html: $(PROGRAM).8
 	@groff -mandoc -Thtml $< >$@
 
+MAN_DATE := $(shell ./util/getrevision.sh -d $(PROGRAM).8.tmpl 2>/dev/null)
 $(PROGRAM).8: $(PROGRAM).8.tmpl
 	@# Add the man page change date and version to the man page
-	@sed -e 's#.TH FLASHROM 8 ".*".*#.TH FLASHROM 8 "$(shell ./util/getrevision.sh -d $(PROGRAM).8.tmpl 2>/dev/null)" "$(VERSION)"#' <$< >$@
+	@sed -e 's#.TH FLASHROM 8 .*#.TH FLASHROM 8 "$(MAN_DATE)" "$(VERSION)" "$(MAN_DATE)"#' <$< >$@
 
 install: $(PROGRAM)$(EXEC_SUFFIX) $(PROGRAM).8
 	mkdir -p $(DESTDIR)$(PREFIX)/sbin
@@ -1358,12 +1364,17 @@  install: $(PROGRAM)$(EXEC_SUFFIX) $(PROGRAM).8
 	$(INSTALL) -m 0644 $(PROGRAM).8 $(DESTDIR)$(MANDIR)/man8
 
 export: $(PROGRAM).8
-	@rm -rf $(EXPORTDIR)/flashrom-$(RELEASENAME)
-	@svn export -r BASE . $(EXPORTDIR)/flashrom-$(RELEASENAME)
-	@sed "s/^SVNVERSION.*/SVNVERSION := $(SVNVERSION)/" Makefile >$(EXPORTDIR)/flashrom-$(RELEASENAME)/Makefile
-	@cp $(PROGRAM).8 "$(EXPORTDIR)/flashrom-$(RELEASENAME)/$(PROGRAM).8"
-	@svn log >$(EXPORTDIR)/flashrom-$(RELEASENAME)/ChangeLog
-	@echo Exported $(EXPORTDIR)/flashrom-$(RELEASENAME)/
+	@rm -rf "$(EXPORTDIR)/flashrom-$(RELEASENAME)"
+	@mkdir -p "$(EXPORTDIR)/flashrom-$(RELEASENAME)"
+	@git archive HEAD | tar -x -C "$(EXPORTDIR)/flashrom-$(RELEASENAME)"
+	@sed	-e  's/^VERSION :=.*/VERSION := $(VERSION)/' \
+		-e 's/^MAN_DATE :=.*/MAN_DATE := $(MAN_DATE)/' \
+		-e 's#./util/getrevision.sh -c#false#' \
+		Makefile >"$(EXPORTDIR)/flashrom-$(RELEASENAME)/Makefile"
+	@for f in `git ls-tree -r -t --full-name --name-only HEAD` ; do \
+		touch -d `git log --pretty=format:%cI -1 HEAD -- "$$f"` "$(EXPORTDIR)/flashrom-$(RELEASENAME)/$$f"; \
+	done
+	@echo "Exported $(EXPORTDIR)/flashrom-$(RELEASENAME)/"
 
 tarball: export
 	@tar cjf $(EXPORTDIR)/flashrom-$(RELEASENAME).tar.bz2 -C $(EXPORTDIR)/ $(TAROPTIONS) flashrom-$(RELEASENAME)/
diff --git a/flashrom.c b/flashrom.c
index 25e53f2..1a43303 100644
--- a/flashrom.c
+++ b/flashrom.c
@@ -1801,7 +1801,7 @@  void print_buildinfo(void)
 
 void print_version(void)
 {
-	msg_ginfo("flashrom v%s", flashrom_version);
+	msg_ginfo("flashrom %s", flashrom_version);
 	print_sysinfo();
 	msg_ginfo("\n");
 }
diff --git a/util/getrevision.sh b/util/getrevision.sh
index 1012058..1240e95 100755
--- a/util/getrevision.sh
+++ b/util/getrevision.sh
@@ -5,7 +5,7 @@ 
 # Copyright (C) 2005 coresystems GmbH <stepan@coresystems.de>
 # Copyright (C) 2009,2010 Carl-Daniel Hailfinger
 # Copyright (C) 2010 Chromium OS Authors
-# Copyright (C) 2013 Stefan Tauner
+# 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
@@ -30,31 +30,73 @@  export LC_ALL=C
 # nor local times or dates
 export TZ=UTC0
 
-# Helper functions
-# First argument is the path to inspect (usually optional; w/o it the whole repository will be considered)
-svn_has_local_changes() {
-	svn status "$1" | egrep '^ *[ADMR] *' >/dev/null
+# 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 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"
 }
 
-svn_is_file_tracked() {
-	svn info "$1" >/dev/null 2>&1
-}
-
 git_is_file_tracked() {
 	git ls-files --error-unmatch -- "$1" >/dev/null 2>&1
 }
 
 is_file_tracked() {
-	svn_is_file_tracked "$1" || git_is_file_tracked "$1"
+	git_is_file_tracked "$1"
 }
 
 # Tries to find a remote source for the changes committed locally.
@@ -89,13 +131,7 @@  git_url() {
 scm_url() {
 	local url=
 
-	# for a primitive VCS like subversion finding the URL is easy: there is only one upstream host
-	if svn_is_file_tracked "$1" ; then
-		url="$(svn info "$1" 2>/dev/null |
-		       grep URL: |
-		       sed 's/.*URL:[[:blank:]]*//;s/:\/\/.*@/:\/\//' |
-		       grep ^.)"
-	elif git_is_file_tracked "$1" ; then
+	if git_is_file_tracked "$1" ; then
 		url="$(git_url "$1")"
 	else
 		return ${EXIT_FAILURE}
@@ -116,29 +152,7 @@  timestamp() {
 	# 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 svn_is_file_tracked "$2" ; then
-		if svn_has_local_changes "$2"; then
-			t=$(date -u "$1")
-		else
-			# No local changes, get date of the last log record. Subversion provides that in
-			# ISO 8601 format when using the --xml switch. The sed call extracts that ignoring any
-			# fractional parts started by a comma or a dot.
-			local last_commit_date="$(svn info --xml "$2"| \
-						  sed -n -e 's/<date>\([^,\.]*\)\([\.,].*\)*Z<\/date>/\1Z/p')"
-
-			case $(uname) in
-			# Most BSD dates do not support parsing date values from user input with -d but all of
-			# them support parsing the syntax with [[[[[[cc]yy]mm]dd]HH]MM[.ss]]. We have to
-			# transform the ISO8601 date first though.
-			NetBSD|OpenBSD|DragonFly|FreeBSD)
-				last_commit_date="$(echo ${last_commit_date} | \
-				   sed -n -e 's/\(....\)-\(..\)-\(..\)T\(..\):\(..\):\(..\)Z/\1\2\3\4\5\.\6/p')"
-				t=$(date -u -j "${last_commit_date}" "$1" 2>/dev/null);;
-			*)
-				t=$(date -u -d "${last_commit_date}" "$1" 2>/dev/null);;
-			esac
-		fi
-	elif git_is_file_tracked "$2" ; then
+	if git_is_file_tracked "$2" ; then
 		# are there local changes?
 		if git_has_local_changes "$2" ; then
 			t=$(date -u "${1}")
@@ -163,55 +177,102 @@  timestamp() {
 	echo "${t}"
 }
 
-# Retrieve local SCM revision info. This is useful if we're working in a different SCM than upstream and/or
-# have local changes.
-local_revision() {
-	local r=
-
-	if svn_is_file_tracked "$1" ; then
-		r=$(svn_has_local_changes "$1" && echo "dirty")
-	elif git_is_file_tracked "$1" ; then
-		r=$(git_last_commit "$1")
-
-		local svn_base=$(git log --grep=git-svn-id -1 --format='%h')
-		if [ "$svn_base" != "" ] ; then
-			local diff_to_svn=$(git rev-list --count ${svn_base}..${r})
-			if [ "$diff_to_svn" -gt 0 ] ; then
-				r="$r-$diff_to_svn"
-			fi
-		fi
+tag() {
+	local t=
 
-		if git_has_local_changes "$1" ; then
-			r="$r-dirty"
+	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
-		return ${EXIT_FAILURE}
+		update || { echo "offline" ; return ; }
+		up_refs=$upcache_refs
 	fi
 
-	echo "${r}"
-}
+	# 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
 
-# Get the upstream flashrom revision stored in SVN metadata.
-upstream_revision() {
-	local r=
-
-	if svn_is_file_tracked "$1" ; then
-		r=$(svn info "$1" 2>/dev/null | \
-		    grep "Last Changed Rev:" | \
-		    sed -e "s/^Last Changed Rev: *//" -e "s/\([0-9]*\).*/r\1/" | \
-		    grep "r[0-9]")
-	elif git_is_file_tracked "$1" ; then
-		# If this is a "native" git-svn clone we could use git svn log:
-		# git svn log --oneline -1 | sed 's/^r//;s/[[:blank:]].*//' or even git svn find-rev
-		# but it is easier to just grep for the git-svn-id unconditionally
-		r=$(git log --grep=git-svn-id -1 -- "$1" | \
-		    grep git-svn-id | \
-		    sed 's/.*@/r/;s/[[:blank:]].*//')
+	# 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
 
-	if [ -z "$r" ]; then
-		r="unknown" # default to unknown
+	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}"
 }
 
@@ -228,16 +289,20 @@  Commands
         this message
     -c or --check
         test if path is under version control at all
-    -l or --local
-        local revision information including an indicator for uncommitted changes
-    -u or --upstream
-        upstream revision
-    -U or --url
+    -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
 }
@@ -260,15 +325,15 @@  main() {
 		-h|--help)
 			action=show_help;
 			shift;;
-		-l|--local)
+		-T|--tag)
 			check_action $1
-			action=local_revision
+			action=tag
 			shift;;
-		-u|--upstream)
+		-r|--revision)
 			check_action $1
-			action=upstream_revision
+			action=revision
 			shift;;
-		-U|--url)
+		-u|--url)
 			check_action $1
 			action=scm_url
 			shift;;
@@ -280,9 +345,17 @@  main() {
 			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"
+			check_action $1
+			action=is_tracked
 			shift;;
 		-*)
 			show_help;
diff --git a/util/git-hooks/applypatch-msg b/util/git-hooks/applypatch-msg
new file mode 100755
index 0000000..32ff6c7
--- /dev/null
+++ b/util/git-hooks/applypatch-msg
@@ -0,0 +1,15 @@ 
+#!/bin/sh
+#
+# A hook script to check the commit log message taken by
+# applypatch from an e-mail message (via git-am).
+# We simply do the same as for other commit messages
+#
+# The hook should exit with non-zero status after issuing an
+# appropriate message if it wants to stop the commit.  The hook is
+# allowed to edit the commit message file.
+#
+
+. git-sh-setup
+test -x "$GIT_DIR/hooks/commit-msg" &&
+	exec "$GIT_DIR/hooks/commit-msg" ${1+"$@"}
+:
diff --git a/util/git-hooks/commit-msg b/util/git-hooks/commit-msg
new file mode 100755
index 0000000..710fe78
--- /dev/null
+++ b/util/git-hooks/commit-msg
@@ -0,0 +1,15 @@ 
+#!/bin/sh
+#
+# A hook script to check the commit log message.
+# Called by "git commit" with one argument, the name of the file
+# that has the commit message.  The hook should exit with non-zero
+# status after issuing an appropriate message if it wants to stop the
+# commit.  The hook is allowed to edit the commit message file.
+#
+
+# Catches duplicate Signed-off-by lines.
+test "" = "$(grep '^Signed-off-by: ' "$1" |
+	 sort | uniq -c | sed -e '/^[ 	]*1[ 	]/d')" || {
+	echo "Duplicate Signed-off-by lines." >&2
+	exit 1
+}
diff --git a/util/git-hooks/install.sh b/util/git-hooks/install.sh
new file mode 100755
index 0000000..84151d0
--- /dev/null
+++ b/util/git-hooks/install.sh
@@ -0,0 +1,17 @@ 
+#!/bin/sh -e
+
+root=$(git rev-parse --show-cdup 2>/dev/null) || ( echo "Not under git control. Cannot install git hooks." >&2 ; exit 0 )
+dst="${root}"$(git rev-parse --git-path hooks/)
+src=util/git-hooks/ # relative to root
+hooks=$(cd "${root}${src}" && git ls-files -c | grep -Ev 'install.sh|wrapper.sh')
+
+for h in $hooks; do
+	# Test if hook is not already installed, i.e. doesn't point at the wrapper
+	if [ ! "${dst}$h" -ef "${root}${src}wrapper.sh" ]; then
+		# preserve custom hooks if any
+		if [ -e "${dst}$h" ]; then
+			mv "${dst}$h" "${dst}$h.local"
+		fi
+		ln -s "../../${src}wrapper.sh" "${dst}$h"
+	fi
+done
diff --git a/util/git-hooks/pre-applypatch b/util/git-hooks/pre-applypatch
new file mode 100755
index 0000000..2ed28f7
--- /dev/null
+++ b/util/git-hooks/pre-applypatch
@@ -0,0 +1,12 @@ 
+#!/bin/sh
+#
+# An example hook script to verify what is about to be committed
+# by applypatch from an e-mail message.
+#
+# The hook should exit with non-zero status after issuing an
+# appropriate message if it wants to stop the commit.
+
+. git-sh-setup
+test -x "$GIT_DIR/hooks/pre-commit" &&
+	exec "$GIT_DIR/hooks/pre-commit" ${1+"$@"}
+:
diff --git a/util/git-hooks/pre-commit b/util/git-hooks/pre-commit
new file mode 100755
index 0000000..dbccb9e
--- /dev/null
+++ b/util/git-hooks/pre-commit
@@ -0,0 +1,9 @@ 
+#!/bin/sh
+#
+# A hook script to verify what is about to be committed.
+# Called by "git commit" with no arguments.  The hook should
+# exit with non-zero status after issuing an appropriate message if
+# it wants to stop the commit.
+
+# Check for whitespace errors
+git diff-index --check --cached HEAD -- || exit 1
diff --git a/util/git-hooks/pre-push b/util/git-hooks/pre-push
new file mode 100755
index 0000000..3fab8c4
--- /dev/null
+++ b/util/git-hooks/pre-push
@@ -0,0 +1,74 @@ 
+#!/bin/sh
+
+# A hook script to verify what is about to be pushed.  Called by "git
+# push" after it has checked the remote status, but before anything has been
+# pushed.  If this script exits with a non-zero status nothing will be pushed.
+#
+# This hook is called with the following parameters:
+#
+# $1 -- Name of the remote to which the push is being done
+# $2 -- URL to which the push is being done
+#
+# If pushing without using a named remote those arguments will be equal.
+#
+# Information about the commits which are being pushed is supplied as lines to
+# the standard input in the form:
+#
+#   <local ref> <local sha1> <remote ref> <remote sha1>
+
+remote="$1"
+url="$2"
+
+zero=0000000000000000000000000000000000000000
+
+upstream_pattern="flashrom.org.*git/flashrom.git"
+precious_branches=( stable staging )
+
+# Only care about the upstream repository
+if echo "$url" | grep -q -v -E "$upstream_pattern" ; then
+	exit 0
+fi
+
+while read local_ref local_sha remote_ref remote_sha ; do
+	if [ "$remote_ref" != "refs/heads/staging" -a "$remote_ref" != "refs/heads/stable" ]; then
+		echo "Feature branches not allowed ($remote_ref)." >&2
+		exit 1
+	fi
+
+	if [ "$local_sha" = $zero ]; then
+		echo "Deletion of branches is prohibited." >&2
+		exit 1
+	else
+		if [ "$remote_sha" = "$zero" ]; then
+			echo "No new branches allowed." >&2
+			exit 1
+		else
+			range="$remote_sha..$local_sha"
+		fi
+
+		# Check for Signed-off-by and Acked-by
+		commit=$(git rev-list -n 1 --all-match --invert-grep -E \
+			--grep '^Signed-off-by: .+ <.+@.+\..+>$' --grep '^Acked-by: .+ <.+@.+\..+>$' "$range")
+		if [ -n "$commit" ]; then
+			echo "No Signoff or Ack found in commit $local_sha in $local_ref, not pushing." >&2
+			exit 1
+		fi
+
+		# Make _really_ sure we do not rewrite precious history
+		for lbranch in "${precious_branches[@]}"; do
+			if [ "$remote_ref" = "refs/heads/$lbranch" ]; then
+				nonreachable=$(git rev-list $remote_sha ^$local_sha)
+				if [ -n "$nonreachable" ]; then
+					echo "Only fast-forward pushes are allowed on $lbranch." >&2
+					echo "$nonreachable is not included in $remote_sha while pusing to $remote_ref" >&2
+					exit 1
+				fi
+			fi
+		done
+
+		# FIXME: check commit log format (subject without full stop at the end etc).
+		# FIXME: do buildbot checks if authorized?
+	fi
+done
+
+exit 0
diff --git a/util/git-hooks/wrapper.sh b/util/git-hooks/wrapper.sh
new file mode 100755
index 0000000..3fc3e05
--- /dev/null
+++ b/util/git-hooks/wrapper.sh
@@ -0,0 +1,10 @@ 
+#!/bin/sh
+
+if [ -x $0.local ]; then
+	$0.local "$@" || exit $?
+fi
+
+hook=$(git rev-parse --show-toplevel)"/util/git-hooks/"$(basename $0)
+if [ -x $hook ]; then
+	$hook "$@" || exit $?
+fi