summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorOswald Buddenhagen <oswald.buddenhagen@nokia.com>2011-05-02 11:21:24 +0200
committerOswald Buddenhagen <oswald.buddenhagen@nokia.com>2011-05-05 17:18:48 +0200
commit76da032c12b377d27150389a6c806d89234c545a (patch)
tree11a4d1f0bb2969e7c9128c629996d9e478ba1671
initial commit
-rw-r--r--LICENSE29
-rwxr-xr-xgit-bin/git-edit-commit87
-rwxr-xr-xgit-bin/git-note-review115
-rwxr-xr-xgit-bin/git-pasteapply66
-rwxr-xr-xgit-bin/git-pastebin204
-rwxr-xr-xgit-bin/git-rewrite-author152
-rwxr-xr-xgit-hooks/git_post_commit_hook19
-rwxr-xr-xgit-hooks/sanitize-commit385
8 files changed, 1057 insertions, 0 deletions
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..f53c6af
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,29 @@
+Unless noted otherwise, the following 3-clause BSD-style license
+applies to the scripts in this package:
+
+Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+2. Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+3. Neither the name of the University nor the names of its contributors
+ may be used to endorse or promote products derived from this software
+ without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGE.
diff --git a/git-bin/git-edit-commit b/git-bin/git-edit-commit
new file mode 100755
index 0000000..c51e0be
--- /dev/null
+++ b/git-bin/git-edit-commit
@@ -0,0 +1,87 @@
+#!/bin/sh
+# Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
+# All rights reserved.
+#
+# Contact: Nokia Corporation <info@qt.nokia.com>
+#
+# You may use this file under the terms of the 3-clause BSD license.
+# See the file LICENSE from this package for details.
+#
+
+if test "x$EDITOR" = "x$0" ; then
+ # The script was launched as an $EDITOR from git rebase -i.
+ # Modify the pick line to an edit line and just exit.
+ sed -e '1,$s/^pick '"$SHORT_SHA1 /edit $SHORT_SHA1 /" < "$1" >/tmp/editcommit$$
+ mv /tmp/editcommit$$ "$1"
+ exit 0
+fi
+
+# Extract the SHA-1 from the command-line and validate it.
+MODE=message
+if test "x$1" = "x--source" ; then
+ MODE=source
+ shift
+fi
+if test -z "$1" ; then
+ echo "Usage: $0 [--source] sha-1"
+ echo ""
+ echo "git-edit-commit allows you to edit a git commit to change"
+ echo "the commit message, even if it is several hundred changes back."
+ echo "It is equivalent to 'git rebase -i', but slightly safer."
+ echo ""
+ echo "The --source option specifies that you want to edit the source"
+ echo "as well as the commit message. This starts a 'git rebase -i'"
+ echo "which you will then have to perform manually."
+ exit 1
+fi
+SHA1="$1"
+if ! git merge-base "$SHA1" HEAD >/dev/null 2>/dev/null ; then
+ echo "$SHA1 does not appear to be in the current branch"
+ exit 1
+fi
+
+# Get the current branch.
+CURRENTBRANCH=`git branch | grep '^\*' | awk '{print $2}' -`
+if test "$CURRENTBRANCH" = "(no" ; then
+ echo "Not on a valid branch - please check out the correct branch"
+ exit 1
+fi
+
+if test -n "`git diff HEAD`" ; then
+ echo "You have uncommited changes. Please commit or stash them first." >&2
+ exit 1
+fi
+
+# Replace aliases like HEAD and HEAD^ with the actual SHA-1.
+SHA1=`git rev-parse "$SHA1"`
+SHORT_SHA1=`git rev-parse --short "$SHA1"`
+
+# Check that the change hasn't already been pushed.
+COMMON=`git merge-base "$SHA1" "origin/$CURRENTBRANCH" 2>/dev/null`
+if test "$COMMON" = "$SHA1" ; then
+ echo "$SHA1 has already been pushed - cannot edit it"
+ exit 1
+fi
+
+EDITOR="$0"
+export EDITOR
+export SHORT_SHA1
+if ! git rebase --preserve-merges -i "${SHA1}^" ; then
+ exit 1
+fi
+unset EDITOR
+
+# If doing a source rebase, then just start it up at the right point.
+if test "$MODE" = "source" ; then
+ echo "To make the source for your chosen commit editable, run:"
+ echo ""
+ echo " git reset HEAD^"
+ echo ""
+ exit 0
+fi
+
+git commit --amend
+
+git rebase --continue
+
+exit 0
diff --git a/git-bin/git-note-review b/git-bin/git-note-review
new file mode 100755
index 0000000..53e6065
--- /dev/null
+++ b/git-bin/git-note-review
@@ -0,0 +1,115 @@
+#! /bin/sh
+# Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
+# All rights reserved.
+#
+# Contact: Nokia Corporation <info@qt.nokia.com>
+#
+# You may use this file under the terms of the 3-clause BSD license.
+# See the file LICENSE from this package for details.
+#
+
+if test -z "$2"; then
+ cat >&2 <<EOF
+Usage: $0 <user> <sha-1>...
+
+git-note-review adds Reviewed-by: lines to the log messages of the
+named commits. Commits can be specified as either as a single sha1 or
+as a sha1_excl..sha1_incl range (see git rev-list).
+If any <whatever>-by: tag with the specified <user> is found to be
+already present in a given commit, the commit is skipped.
+EOF
+ exit 1
+fi
+
+GIT_DIR=$(git rev-parse --git-dir 2>/dev/null)
+if [ -z "$GIT_DIR" ]; then
+ echo >&2 "Not a git repository."
+ exit 1
+fi
+cd "$GIT_DIR/.."
+
+REF=$(git symbolic-ref HEAD 2>/dev/null)
+if [ -z "$REF" ]; then
+ echo >&2 "Not on a branch."
+ exit 1
+fi
+
+if test -n "`git diff HEAD`" ; then
+ echo "You have uncommited changes. Please commit or stash them first." >&2
+ exit 1
+fi
+
+who=$1
+shift
+SHA1s=
+firstSHA1=
+fail=false
+for j in "$@"; do
+ if test -z "${j##*..*}"; then
+ if ! jj=`git rev-list $j 2> /dev/null`; then
+ echo "Cannot parse commit range $j." >&2
+ fail=true
+ continue
+ fi
+ j=$jj
+ fi
+ for ii in $j; do
+ if ! i=`git rev-parse --short $ii 2> /dev/null`; then
+ echo "Cannot parse commit id $ii." >&2
+ fail=true
+ continue
+ fi
+ if test -n "`git rev-list -1 $i ^HEAD`"; then
+ echo "Commit $i is not on current branch. Did you rebase it meanwhile?" >&2
+ fail=true
+ continue
+ fi
+ if git log -1 --pretty=%b $i | egrep -q -i "^[-[:alpha:]]+-by: *$who\$"; then
+ echo "Commit $i already noted as reviewed by $who."
+ else
+ if test -z "$firstSHA1"; then
+ firstSHA1=`git rev-parse $i`
+ SHA1s=$i
+ else
+ firstSHA1=`git merge-base $firstSHA1 $i`
+ SHA1s="$SHA1s|$i"
+ fi
+ fi
+ done
+done
+if $fail; then
+ echo "Exiting due to fatal errors." >&2
+ exit 1
+fi
+if test -z "$SHA1s"; then
+ echo "No commits to change." >&2
+ exit 0
+fi
+
+# This would be pretty much bullet-proof, but also a lot slower.
+#if test -z "$(git rev-list -1 $firstSHA1 --not $(git for-each-ref --format='%(objectname)' refs/remotes/))"; then
+# echo "$firstSHA1 has already been pushed - cannot edit it." >&2
+# exit 1
+#fi
+# This should be Good Enough (TM), and it is rather fast.
+if test -n "$(git rev-list -1 $firstSHA1^..@{upstream})"; then
+ echo "Commit `git rev-parse --short $firstSHA1` has already been pushed - cannot edit it." >&2
+ exit 1
+fi
+
+git filter-branch --force --original refs/note-review-backup --msg-filter '
+ if echo $GIT_COMMIT | egrep -q "^('"$SHA1s"')"; then
+ sed "s/^\\(Reviewed-by:\\) *\\(pending\\|tbd\\|TBD\\)\$/\\1 '"$who"'/;t nx
+\${
+/^[-[:alpha:]]\+:/!a\\
+
+a\\
+Reviewed-by: '"$who"'
+}
+b
+:nx;n;b nx"
+ else
+ cat
+ fi
+' $firstSHA1^..$REF || exit 1
+rm -rf $GIT_DIR/refs/note-review-backup # we have the reflog, so what
diff --git a/git-bin/git-pasteapply b/git-bin/git-pasteapply
new file mode 100755
index 0000000..d8f9044
--- /dev/null
+++ b/git-bin/git-pasteapply
@@ -0,0 +1,66 @@
+#!/usr/bin/env perl
+# Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
+# All rights reserved.
+#
+# Contact: Nokia Corporation <info@qt.nokia.com>
+#
+# You may use this file under the terms of the 3-clause BSD license.
+# See the file LICENSE from this package for details.
+#
+
+use strict;
+use WWW::Mechanize;
+use Config;
+
+unless (scalar @ARGV) {
+ print "Usage: git pasteapply [options] <number> [<number>...]\n" .
+ "Fetches the paste from pastebin.ca numbered <number> and applies\n" .
+ "Options are passed directly to git am\n" .
+ "\n" .
+ "Useful options:\n" .
+ " -s, --signoff Add Signed-off-by: line to the commit message\n" .
+ " -3, --3way When the patch does not apply cleanly, fall back on 3-way merge\n" .
+ " -p<n> Strip <n> elements of the paths (default 1)\n" .
+ " --whitespace=<nowarn,warn,fix,error,error-all>\n";
+ exit 0;
+}
+my @pastes;
+my @args;
+
+for (@ARGV) {
+ if (m/^-/) {
+ push @args, $_;
+ } else {
+ push @pastes, $_;
+ }
+}
+
+open GIT_AM, "|-", "git", "am", @args, "-"
+ or die "Cannot start git-am: $!";
+
+my $www = WWW::Mechanize->new();
+foreach my $paste (@pastes) {
+ my $url = pastebin_url($paste);
+ print "Applying $url\n";
+ $www->get($url);
+ my $content = $www->content();
+ $content =~ s/\r\n/\n/g;
+ print GIT_AM $content;
+}
+
+close GIT_AM;
+exit $?;
+
+sub pastebin_url($) {
+ my $arg = $_[0];
+ return "http://pastebin.ca/raw/$3"
+ if ($arg =~ m,^(https?://)?(.*\.)?pastebin\.ca/([0-9]+),);
+ return "http://pastebin.com/download.php?i=$3"
+ if ($arg =~ m,^(https?://)?(.*\.)?pastebin.com/([a-zA-Z0-9]+)$,);
+
+ return $arg if ($arg =~ m,^http://,); # Assume it's plain text...
+
+ # No http:// prefix
+ return "http://pastebin.ca/raw/$arg" if ($ENV{PASTEBIN} =~ /pastebin\.ca/);
+ return "http://pastebin.com/download.php?i=$arg"; # Fallback, assume pastebin.com
+}
diff --git a/git-bin/git-pastebin b/git-bin/git-pastebin
new file mode 100755
index 0000000..7ee7bbb
--- /dev/null
+++ b/git-bin/git-pastebin
@@ -0,0 +1,204 @@
+#!/usr/bin/env perl
+# Arguments are:
+# git pastebin [<commit>]
+#
+
+# Copyright (C) Thiago Macieira <thiago@kde.org>
+# Additional fixes by Giuseppe D'Angelo <dangelog@gmail.com>
+# This program is based on paste2pastebin.pl, that is
+# Copyright (C) Fred Blaise <chapeaurouge_at_madpenguin_dot_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 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+use strict;
+use LWP::UserAgent;
+use WWW::Mechanize;
+
+my $description;
+my $content;
+my $squash = -1;
+my @committish;
+my @fpargs;
+my @pathargs;
+my $dashdash = 0;
+
+while (scalar @ARGV) {
+ my $arg = shift @ARGV;
+
+ if ($arg eq "-d" || $arg eq "--description") {
+ die "Option $arg requires an argument" unless scalar @ARGV;
+ $description = shift @ARGV;
+ } elsif ($arg eq "-s" || $arg eq "--squash") {
+ $squash = 1;
+ } elsif ($arg eq "--no-squash") {
+ $squash = 0;
+ } elsif ($arg eq "--") {
+ $dashdash = 1;
+ } elsif ($arg =~ m/^-/) {
+ push @fpargs, $arg;
+ } elsif ($dashdash) {
+ push @pathargs, $arg;
+ } else {
+ push @committish, $arg;
+ }
+}
+
+# Squash by default if description was given
+$squash = $description if ($squash == -1);
+
+# Default revision is HEAD
+push @committish, "HEAD" unless (scalar @committish);
+
+# Prepend -- if pathargs isn't empty
+unshift @pathargs, "--" if (scalar @pathargs);
+
+# Try to parse this commit
+my @revlist;
+my $needsrevlist = 0;
+open REVPARSE, "-|", "git", "rev-parse", @committish;
+while (<REVPARSE>) {
+ chomp;
+ push @revlist, $_;
+ $needsrevlist = 1 if (m/^\^/);
+}
+close REVPARSE;
+
+if ($needsrevlist) {
+ # Get the revision list then
+ open REVLIST, "-|", "git", "rev-list", "--reverse", @revlist
+ or die "Cannot run git-rev-list: $!";
+ @revlist = ();
+ while (<REVLIST>) {
+ chomp;
+ push @revlist, $_;
+ }
+ close REVLIST;
+}
+
+# Are we squashing?
+if (scalar @revlist > 1 && $squash) {
+ # Yes, this one is easy
+ open FORMATPATCH, "-|", "git", "format-patch", "--stdout", @fpargs, @committish, @pathargs
+ or die "Cannot run git format-patch: $!";
+
+ while (<FORMATPATCH>) {
+ $content .= $_;
+ }
+ close FORMATPATCH;
+
+ submit($description, $content);
+ exit(0);
+}
+
+# No, we're not squashing
+# Iterate over the commits
+for my $rev (@revlist) {
+ $content = "";
+ $description = "";
+
+ # Get description
+ open PRETTY, "-|", "git", "log", "--pretty=format:%s%n%b", "$rev^!"
+ or die "Cannot get description for $rev: $!";
+ while (<PRETTY>) {
+ $description .= $_;
+ }
+ close PRETTY;
+
+ # Get patch
+ open FORMATPATCH, "-|", "git", "format-patch", "--stdout", @fpargs, "$rev^!", @pathargs
+ or die "Cannot get patch for $rev: $!";
+ while (<FORMATPATCH>) {
+ $content .= $_;
+ }
+ close FORMATPATCH;
+
+ submit($description, $content);
+}
+
+sub submit($$) {
+ if ($ENV{PASTEBIN} =~ /pastebin\.ca/) {
+ submit_pastebinca(@_);
+ } elsif ($ENV{PASTEBIN} =~ /pastebin.com/) {
+ submit_pastebincom(@_);
+ } elsif (!$ENV{PASTEBIN}) {
+ submit_default(@_);
+ } else {
+ die "Sorry, I don't know how to talk to $ENV{PASTEBIN}."
+ }
+}
+
+sub submit_default($$) {
+ submit_pastebincom(@_);
+}
+
+sub submit_pastebincom($$) {
+ my ($description, $content) = @_;
+ my $paste_subdomain;
+ $paste_subdomain = $2 if ($ENV{PASTEBIN} =~ m,(https?://)?(.*)\.pastebin\.com,);
+
+ my %fields = (
+ paste_code => $content,
+ paste_subdomain => $paste_subdomain,
+ paste_format => 'diff',
+ paste_name => $ENV{PASTEBIN_NAME}
+ );
+
+ my $agent = LWP::UserAgent->new();
+ my $api = 'http://pastebin.com/api_public.php';
+
+ my $reply = $agent->post($api, \%fields);
+
+ die "Could not send paste: $!" unless ($reply->is_success);
+
+ my $reply_content = $reply->decoded_content;
+
+ # actually, pastebin.com is so dumb that it returns
+ # HTTP 200 OK even in case of error; the content then contains an error message...
+ die "Could not send paste: $reply_content" if ($reply_content =~ /^ERROR/);
+
+ print $reply_content, "\n";
+}
+
+sub submit_pastebinca($$) {
+ my ($description, $content) = @_;
+ my %fields = (
+ content => $content,
+ description => $description,
+ type => '34', # Unfortunately, "Diff/Patch" is missing in the quiet interface
+ expiry => '1 month',
+ name => '',
+ );
+ my $www = WWW::Mechanize->new();
+
+ my $api_key = "1OIIB1/PYzkNe/azsjOFkm4iBe0414V0";
+ my $pastebin_url = "http://pastebin.ca/quiet-paste.php?api=$api_key";
+ my $pastebin_root = "http://pastebin.ca";
+
+ $www->get($pastebin_url);
+ die "Cannot get the pastebin form: $!" unless ($www->success());
+
+ $www->form_name('pasteform');
+ $www->set_fields(%fields);
+ $www->submit_form(button => 's');
+
+ die "Could not send paste: $!" unless ($www->success());
+
+ $content = $www->content();
+ if($content =~ m/^SUCCESS:(.*)/) {
+ print "$pastebin_root/$1\n";
+ } else {
+ print $content;
+ }
+}
diff --git a/git-bin/git-rewrite-author b/git-bin/git-rewrite-author
new file mode 100755
index 0000000..f58f937
--- /dev/null
+++ b/git-bin/git-rewrite-author
@@ -0,0 +1,152 @@
+#!/usr/bin/env perl
+# Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
+# All rights reserved.
+#
+# Contact: Nokia Corporation <info@qt.nokia.com>
+#
+# You may use this file under the terms of the 3-clause BSD license.
+# See the file LICENSE from this package for details.
+#
+
+use strict;
+use warnings;
+
+use Getopt::Long;
+
+sub usage {
+ print STDERR
+q{Usage: git rewrite-author --from <from> --to <to> [ -f | --force ]
+ [--] [<rev-list options>...]
+
+ Replace author and committer field in some commits.
+
+
+EXAMPLE:
+
+ You've accidentally made some commits like this:
+
+ Author: bjones <bjones@localhost.localdomain>
+
+ ... where it should have been:
+
+ Author: Bob Jones <bob.jones@example.com>
+
+ The first bad commit is a1b2c3d4...
+
+ Fix it up by:
+
+ $ git rewrite-author --from "bjones <bjones@localhost.localdomain>" \
+ --to "Bob Jones <bob.jones@example.com>" a1b2c3d4^..HEAD
+
+ If you omit a range of commits, this command will scan _all_ commits
+ leading up to HEAD, which will take a long time for repositories with
+ a large history.
+
+ WARNING: this script will change the SHA1 of every commit including
+ and following the first rewritten commit. You can't do this for commits
+ you've already pushed unless you really know what you're doing!
+};
+}
+
+sub parse_author
+{
+ my $author = shift;
+ if ($author =~ /^(.*?) <([^>]+)>$/) {
+ return ($1, $2);
+ }
+ else {
+ die "Could not parse `$author'; expected something like \"Some Name <some.address\@example.com>\"";
+ }
+}
+
+
+# Return a string single-quoted for use in shell
+sub sh_squote
+{
+ my $out = shift;
+ $out =~ s/'/'"'"'/g;
+ return "'$out'";
+}
+
+# Return fragment of shell code to rewrite a specific env var
+sub rewrite_env
+{
+ my %opts = @_;
+ my $env = $opts{env};
+ my $from = sh_squote($opts{from});
+ my $to = sh_squote($opts{to});
+
+ my $cmd = <<EOF
+if [ "\$$env" = $from ]; then
+ $env=$to;
+ export $env;
+ if [ \$REWROTE = 0 ]; then
+ echo;
+ echo Hit for \$GIT_COMMIT:;
+ REWROTE=1;
+ fi;
+ echo " $env:" $from '->' $to;
+fi
+EOF
+ ;
+
+ $cmd =~ s/\n */ /sg;
+
+ return $cmd;
+}
+
+my $from;
+my $to;
+my $force;
+if (!GetOptions(
+ "from=s" => \$from,
+ "to=s" => \$to,
+ "force" => \$force,
+)) {
+ usage;
+ exit 2;
+}
+
+if (!$from || !$to) {
+ print STDERR "Need `--from' and `--to' arguments\n";
+ usage;
+ exit 2;
+}
+
+my ($from_name, $from_address) = parse_author($from);
+my ($to_name, $to_address) = parse_author($to);
+
+my $env_filter_command = "REWROTE=0; ".
+ rewrite_env(
+ env => 'GIT_AUTHOR_NAME',
+ from => $from_name,
+ to => $to_name,
+ )
+ . "; " .
+ rewrite_env(
+ env => 'GIT_COMMITTER_NAME',
+ from => $from_name,
+ to => $to_name,
+ )
+ . "; " .
+ rewrite_env(
+ env => 'GIT_AUTHOR_EMAIL',
+ from => $from_address,
+ to => $to_address,
+ )
+ . "; " .
+ rewrite_env(
+ env => 'GIT_COMMITTER_EMAIL',
+ from => $from_address,
+ to => $to_address,
+ )
+;
+
+my @git_filter_branch = ("git", "filter-branch", "--env-filter", $env_filter_command);
+if ($force) {
+ push @git_filter_branch, "--force";
+}
+push @git_filter_branch, @ARGV;
+
+exec @git_filter_branch;
+
diff --git a/git-hooks/git_post_commit_hook b/git-hooks/git_post_commit_hook
new file mode 100755
index 0000000..cc47980
--- /dev/null
+++ b/git-hooks/git_post_commit_hook
@@ -0,0 +1,19 @@
+#! /bin/sh
+# Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
+# All rights reserved.
+#
+# Contact: Nokia Corporation <info@qt.nokia.com>
+#
+# You may use this file under the terms of the 3-clause BSD license.
+# See the file LICENSE from this package for details.
+#
+
+# Usage: in every repository you want to have checked:
+# cd .git/hooks
+# ln -s /path/to/git_post_commit_hook post-commit
+#
+
+sha1=${1-HEAD} # just for debugging
+GIT_PUSH=${GIT_PUSH+$GIT_PUSH,}wip # this check makes totally no sense locally
+export GIT_PUSH
+exec sanitize-commit $sha1 strict >&2
diff --git a/git-hooks/sanitize-commit b/git-hooks/sanitize-commit
new file mode 100755
index 0000000..a67fb1c
--- /dev/null
+++ b/git-hooks/sanitize-commit
@@ -0,0 +1,385 @@
+#! /usr/bin/perl -w
+# Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
+# All rights reserved.
+#
+# Contact: Nokia Corporation <info@qt.nokia.com>
+#
+# You may use this file under the terms of the 3-clause BSD license.
+# See the file LICENSE from this package for details.
+#
+
+use strict;
+
+if ($#ARGV < 0 or $#ARGV > 1 || ($#ARGV == 1 && $ARGV[1] ne "strict")) {
+ print STDERR "Usage: $0 <sha1> [strict]\n";
+ exit 2;
+}
+my $sha1 = $ARGV[0];
+my $strict = ($#ARGV == 1);
+
+my %cfg = ();
+if (defined $ENV{GIT_PUSH}) {
+ foreach my $c (split ",", $ENV{GIT_PUSH}) {
+ $cfg{$c} = 1;
+ }
+}
+my $fail = 0;
+my $printed = 0;
+my $file = "";
+my $fail_file = "-";
+my $summary;
+
+sub complain()
+{
+ my ($msg, $key) = @_;
+ my $pfx;
+
+ if (!$printed) {
+ $summary =~ s/^(.{50}).{5,}$/$1\[...]/;
+ print "***\n*** Suspicious changes in commit ".$sha1." (".$summary."):\n";
+ $printed = 1;
+ }
+ if (length($file)) {
+ if ($file ne $fail_file) {
+ print "***\n*** ".$file.":\n";
+ $fail_file = $file;
+ }
+ $pfx = "*** - ";
+ } else {
+ if ($file ne $fail_file) {
+ print "***\n";
+ $fail_file = "";
+ }
+ $pfx = "*** - ";
+ }
+ if (length($key)) {
+ print $pfx.$msg." (key \"".$key."\")\n";
+ $fail = 1;
+ } else {
+ # Hints need no overrides ...
+ print $pfx.$msg."\n";
+ }
+}
+
+my $ln = 0;
+my $iswip = defined($cfg{wip});
+my $badlog = defined($cfg{log});
+open MSG, "git log -1 --pretty=raw ".$sha1." |" or die;
+while (<MSG>) {
+ chomp;
+ s/^ // or next;
+ if ($ln == 0) {
+ $summary = $_;
+ if (!$iswip && /\bWIP\b|\*{3}|^(?:squash|fixup)! |^(.)\1*$/i) {
+ &complain("Apparently pushing a Work In Progress", "wip");
+ $iswip = 1;
+ } elsif (!$iswip && !$badlog && length($_) < 7) {
+ &complain("Log message summary is too short", "log");
+ $badlog = 1;
+ } elsif (!$badlog && length($_) > 120) {
+ &complain("Log message summary is excessively long", "log");
+ $badlog = 1;
+ } elsif (length($_) > 70 && !/^Merge ((remote )?branch '[^ ]+'|branch '[^ ]+' of [^ ]+( into [^ ]+)?)$/) {
+ &complain("Hint: Aim for shorter log message summaries", "");
+ }
+ } elsif ($ln == 1) {
+ if (!$badlog && $_ ne "") {
+ &complain("2nd line of log message is not empty", "log");
+ $badlog = 1;
+ }
+ } else {
+ if (!$iswip && /^Reviewed-by: *(pending|TBD)?$/i) {
+ &complain("Apparently pushing a Work In Progress", "wip");
+ }
+ }
+ $ln++;
+}
+close MSG;
+
+my $chunk = 0;
+my @addi = ();
+my @deli = ();
+my $nonws;
+my $ws;
+my $mixws_check = 0;
+my $lineno = 0;
+my @ws_files = ();
+my %ws_lines = (); # hash of lists
+my $braces = 0;
+my $open_key = qr/\s*#\s*if|.*{/;
+my $close_key = qr/\s*#\s*endif|.*}/;
+my $kill_all_ws = qr/\s+((?:\"(?:\\.|[^\"])*\"|\S)+)/; # Collapse all whitespace not inside strings.
+my $kill_nl_ws = qr/((?:\"(?:\\.|[^\"])*\"|\S)+)\s+/; # Collapse all non-leading whitespace not inside strings.
+
+sub flushChunk()
+{
+ my $loc_nonws = 0;
+ my $nlonly = 1;
+ my ($ai, $di) = (0, 0);
+ while (!$loc_nonws) {
+ my ($a, $d) = ("", "");
+ while ($ai < @addi) {
+ $a = $addi[$ai++];
+ $nlonly = 0 if ($a ne "\n" && $a ne "\r\n");
+ $a =~ s/\s+$//;
+ last if (length($a));
+ }
+ while ($di < @deli) {
+ $d = $deli[$di++];
+ $nlonly = 0 if ($d ne "\n" && $d ne "\r\n");
+ $d =~ s/\s+$//;
+ last if (length($d));
+ }
+ last if (!length($a) && !length($d));
+
+ $a =~ /^$close_key/o and $braces--;
+ $d =~ /^$close_key/o and $braces++;
+ if ($braces) {
+ $a =~ s/$kill_nl_ws/$1/go;
+ $d =~ s/$kill_nl_ws/$1/go;
+ } else {
+ $a =~ s/$kill_all_ws/$1/go;
+ $d =~ s/$kill_all_ws/$1/go;
+ }
+ $loc_nonws = 1 if ($a ne $d);
+ $a =~ /^$open_key/o and $braces++;
+ $d =~ /^$open_key/o and $braces--;
+ }
+ while ($ai < @addi) {
+ my $a = $addi[$ai++];
+ $a =~ /^$close_key/o and $braces--;
+ $a =~ /^$open_key/o and $braces++;
+ }
+ while ($di < @deli) {
+ my $d = $deli[$di++];
+ $d =~ /^$close_key/o and $braces++;
+ $d =~ /^$open_key/o and $braces--;
+ }
+ if ($loc_nonws) {
+ $nonws = 1;
+ } elsif (!$nlonly) {
+ $ws = 1;
+ push @ws_files, $file if (!defined($ws_lines{$file}));
+ push @{$ws_lines{$file}}, $lineno - $#addi;
+ }
+ @addi = @deli = ();
+ $chunk = 0;
+}
+
+sub formatSize($)
+{
+ my $sz = shift;
+ if ($sz >= 10 * 1024 * 1024) {
+ return int($sz / (1024 * 1024))."MeB";
+ } elsif ($sz >= 10 * 1024) {
+ return int($sz / 1024)."KiB";
+ } else {
+ return int($sz)."B";
+ }
+}
+
+my @style_fails = ();
+
+sub styleFail($)
+{
+ my $why = shift;
+ push @style_fails, $lineno.": ".$why;
+}
+
+sub flushFile()
+{
+ if (@style_fails) {
+ if ($strict) {
+ &complain("Style issues", "style");
+ } else {
+ &complain("Hint: Style issues", "");
+ }
+ for my $sf (@style_fails) {
+ print "*** ".$sf."\n";
+ }
+ @style_fails = ();
+ }
+}
+
+my $merge;
+my $new_file;
+my $maybe_bin;
+my $is_submodule;
+my $clike;
+my $size;
+my $line = 1000;
+my $crlf_fail;
+my $conflict_fail;
+my $tabs_check;
+my $ws_check;
+my $ctlkw_check;
+open DIFF, "git diff-tree --no-commit-id --diff-filter=ACMR --src-prefix=\@old\@/ --dst-prefix=\@new\@/ --full-index -r -p --cc -M --root ".$sha1." |" or die;
+while (<DIFF>) {
+ if (/^\+/) {
+ if (/^\+\+\+ /) {
+ # This indicates a text file; binary files have "Binary files ... and ... differ" instead.
+ $maybe_bin = 0;
+ if ($new_file) {
+ $new_file = 0;
+ if (!defined($cfg{generated})) {
+ if ($file =~ /\.(prl|la|pc|ilk)$/i) {
+ &complain("Adding build artifact", "generated");
+ } else {
+ $line = 1;
+ }
+ }
+ }
+ next;
+ }
+ $lineno++;
+ if ($merge) {
+ # Consider only lines which are new relative to both parents, i.e., were added during the merge.
+ s/^\+\+// or next;
+ } else {
+ $_ = substr($_, 1);
+ if ($mixws_check) {
+ push @addi, $_;
+ $chunk = 1;
+ }
+ }
+ if (!$crlf_fail && /\r\n$/) {
+ $crlf_fail = 1;
+ &complain("CRLF line endings", "crlf");
+ }
+ if ($line < 50) {
+ if (/All changes made in this file will be lost|This file is automatically generated|DO NOT EDIT|DO NOT delete this file|[Gg]enerated by|uicgenerated|produced by gperf/) {
+ &complain("Adding generated file", "generated");
+ $line = 1000;
+ }
+ $line++;
+ }
+ if (!$conflict_fail && /^(?:[<>=]){7}( |$)/) {
+ &complain("Unresolved merge conflict", "conflict");
+ $conflict_fail = 1;
+ }
+ if ($ws_check) {
+ styleFail("Space indent followed by a TAB character") if (/^ +\t/);
+ styleFail("TAB character in non-leading whitespace") if (/\S *\t/);
+ styleFail("Trailing whitespace") if (/[ \t]\r?\n$/);
+ if ($tabs_check) {
+ styleFail("Leading tabs") if (/^\t+/);
+ if ($ctlkw_check) {
+ styleFail("Flow control keywords must be followed by a single space")
+ if (/\b(if|for|foreach|Q_FOREACH|while|do|switch)(| +)\(/);
+ }
+ }
+ }
+ } elsif (/^-/) {
+ if ($mixws_check) {
+ /^--- / and next;
+ push @deli, substr($_, 1);
+ $chunk = 1;
+ }
+ } else {
+ flushChunk() if ($chunk);
+ if (/^ /) {
+ $lineno++ if (!$merge || !/^ -/);
+ next;
+ }
+ if ($merge ? /^\@\@\@ -\S+ -\S+ \+(\d+)/ : /^\@\@ -\S+ \+(\d+)/) {
+ $lineno = $1 - 1;
+ next;
+ }
+ if (/^diff /) {
+ flushFile();
+ if (/^diff --git \@old\@\/.+ \@new\@\/(.+)$/) {
+ $merge = 0;
+ } elsif (/^diff --cc (.+)$/) {
+ $merge = 1;
+ } else {
+ print STDERR "Warning: cannot parse diff header '".$_."'\n";
+ next;
+ }
+ $file = $1;
+ #print "*** got file ".$file.".\n";
+ $clike = ($file =~ /\.(c|cc|cpp|c\+\+|cxx|m|mm|h|hpp|hxx|cs|java|js|qs|qml|g|y|ypp|pl|glsl)$/i);
+ $new_file = 0;
+ $maybe_bin = 0;
+ $is_submodule = 0;
+ $crlf_fail = defined($cfg{crlf});
+ $mixws_check = !$merge && $clike && !defined($cfg{mixws});
+ $ws_check = !defined($cfg{style}) && ($file !~ /\.(ts|diff|patch)$|^\.gitmodules$/);
+ $tabs_check = $ws_check && ($file !~ /((^|\/)Makefile\b|\.def$)/);
+ $ctlkw_check = $tabs_check && $clike;
+ $conflict_fail = defined($cfg{conflict});
+ $braces = 0;
+ $line = 1000;
+ next;
+ }
+ if ($maybe_bin && /^Binary files /) {
+ if ($new_file) {
+ if (!defined($cfg{generated}) && $file =~ /\.(obj|o|lib|a|dll|so|exe|exp|qm|pdb|idb|suo)$/i) {
+ &complain("Adding build artifact", "generated");
+ }
+ } else {
+ if (!defined($cfg{giant}) && $size > (2<<20)) {
+ &complain("Changing huge binary file (".formatSize($size)." > 2MeB)", "giant");
+ }
+ }
+ next;
+ }
+ if ($_ eq "new file mode 160000\n") {
+ $is_submodule = 1;
+ next;
+ }
+ if (!$is_submodule && /^index ([\w,]+)\.\.(\w+)(?! 160000)( |$)/) {
+ my ($old_trees, $new_tree) = ($1, $2);
+ #print "*** got index $old_trees $new_tree.\n";
+ $size = `git cat-file -s $new_tree`;
+ my $issrc = $clike || ($file =~ /\.(s|asm|pas|l|m4|bat|cmd|sh|py|qdoc(conf)?)$/i);
+ if ($old_trees =~ /^0{40}(,0{40})*$/) {
+ #print "*** adding ".$file.".\n";
+ if (!$conflict_fail && $file =~ /\.(BACKUP|BASE|LOCAL|REMOTE)\.[^\/]+$/) {
+ &complain("Adding temporary file from merge conflict resolution", "conflict");
+ $conflict_fail = 1;
+ }
+ if (!defined($cfg{alien}) && $file =~ /(\.(sln|vcproj|vcxproj|pro\.user)|(^|\/)(Makefile\.am|CMakeLists\.txt))$/i) {
+ &complain("Warning: Adding alien build system file", "alien");
+ }
+ if ($size > (2<<20)) {
+ if (!defined($cfg{giant})) {
+ &complain("Adding huge file (".formatSize($size)." > 2MeB)", "giant");
+ }
+ } elsif ($size > 50000 && !$issrc && !defined($cfg{size})) {
+ &complain("Warning: Adding big file (".formatSize($size)." > 50kB)", "size");
+ }
+ $size = 0;
+ $new_file = 1;
+ } elsif ($size > 20000 && !$issrc && !defined($cfg{size})) {
+ my $old_size = 0;
+ for my $old_tree (split(/,/, $old_trees)) {
+ my $osz = `git cat-file -s $old_tree`;
+ $old_size = $osz if ($osz > $old_size);
+ }
+ if ($size > $old_size * 3 / 2) {
+ &complain("Warning: Increasing file size by more than 50% (".
+ formatSize($old_size)." => ".formatSize($size).")", "size");
+ }
+ }
+ $maybe_bin = 1;
+ next;
+ }
+ }
+}
+flushFile();
+if ($mixws_check) {
+ flushChunk() if ($chunk);
+ if ($nonws and $ws) {
+ $file = "";
+ if ($strict) {
+ &complain("Mixing whitespace-only changes with other changes", "mixws");
+ } else {
+ &complain("Hint: Mixing whitespace-only changes with other changes", "");
+ }
+ for my $fn (@ws_files) {
+ print "*** WS-only in ".$fn.": ".join(", ", @{$ws_lines{$fn}})."\n";
+ }
+ }
+}
+
+exit $fail