diff options
Diffstat (limited to 'git-tools')
| -rwxr-xr-x | git-tools/hooks/commit-msg | 143 | ||||
| -rwxr-xr-x | git-tools/hooks/pre-commit | 65 | ||||
| -rwxr-xr-x | git-tools/hooks/prepare-commit-msg | 4 | ||||
| -rwxr-xr-x | git-tools/merge.php | 175 | ||||
| -rwxr-xr-x | git-tools/setup_github_network.php | 248 | 
5 files changed, 606 insertions, 29 deletions
diff --git a/git-tools/hooks/commit-msg b/git-tools/hooks/commit-msg index a6777ff9c9..b156d276df 100755 --- a/git-tools/hooks/commit-msg +++ b/git-tools/hooks/commit-msg @@ -11,14 +11,30 @@  #  # ln -s ../../git-tools/hooks/commit-msg \\  #   .git/hooks/commit-msg +# +# You can configure whether invalid commit messages abort commits: +# +# git config phpbb.hooks.commit-msg.fatal true	(abort) +# git config phpbb.hooks.commit-msg.fatal false (warn only, do not abort) +# +# The default is to warn only. +# +# Warning/error messages use color by default if the output is a terminal +# ("output" here is normally standard error when you run git commit). +# To force or disable the use of color: +# +# git config phpbb.hooks.commit-msg.color true	(force color output) +# git config phpbb.hooks.commit-msg.color false (disable color output)  config_ns="phpbb.hooks.commit-msg"; -if [ "$(git config --bool $config_ns.fatal)" = "false" ] +if [ "$(git config --bool $config_ns.fatal)" = "true" ]  then -	fatal=0; -else  	fatal=1; +	severity=Error; +else +	fatal=0; +	severity=Warning;  fi  debug_level=$(git config --int $config_ns.debug || echo 0); @@ -47,20 +63,86 @@ debug()  quit()  { -	if [ $1 -gt 0 ] && [ $1 -ne $ERR_UNKNOWN ] && [ $fatal -eq 0 ] +	if [ $1 -eq 0 ] || [ $1 -eq $ERR_UNKNOWN ]  	then +		# success +		exit 0; +	elif [ $fatal -eq 0 ] +	then +		# problems found but fatal is false +		complain 'Please run `git commit --amend` and fix the problems mentioned.' 1>&2  		exit 0;  	else +		complain "Aborting commit." 1>&2  		exit $1;  	fi  } -msg=$(grep -nE '.{81,}' "$1"); +use_color() +{ +	if [ -z "$use_color_cached" ] +	then +		case $(git config --bool $config_ns.color) +		in +		false) +			use_color_cached=1 +			;; +		true) +			use_color_cached=0 +			;; +		*) +			# tty detection in shell: +			# http://hwi.ath.cx/jsh/list/shext/isatty.sh.html +			tty 0>/dev/stdout >/dev/null 2>&1 +			use_color_cached=$? +			;; +		esac +	fi +	# return value is the flag inverted - +	# if return value is 0, this means use color +	return $use_color_cached +} + +complain() +{ +	if use_color +	then +		# Careful: our argument may include arguments to echo like -n +		# ANSI color codes: +		# http://pueblo.sourceforge.net/doc/manual/ansi_color_codes.html +		printf "\033[31m\033[1m" +		if [ "$1" = "-n" ] +		then +			echo "$@" +			printf "\033[0m" +		else +			# This will print one trailing space. +			# Not sure how to avoid this at the moment. +			echo "$@" $(printf "\033[0m") +		fi +	else +		echo "$@" +	fi +} + +# Check for empty commit message +if ! grep -qv '^#' "$1" +then +	# Commit message is empty (or contains only comments). +	# Let git handle this. +	# It will abort with a message like so: +	# +	# Aborting commit due to empty commit message. +	exit 0 +fi + +msg=$(grep -v '^#' "$1" |grep -nE '.{81,}')  if [ $? -eq 0 ]  then -	echo "The following lines are greater than 80 characters long:\n" >&2; -	echo $msg >&2; +	complain "The following lines are greater than 80 characters long:" >&2; +	complain >&2 +	complain "$msg" >&2;  	quit $ERR_LENGTH;  fi @@ -107,7 +189,19 @@ do  		case $expect in  			"header")  				err=$ERR_HEADER; -				echo "$line" | grep -Eq "^\[(ticket/[0-9]+|feature/$branch_regex|task/$branch_regex)\] [A-Z].+$" +				echo "$line" | grep -Eq "^\[(ticket/[0-9]+|feature/$branch_regex|task/$branch_regex)\] .+$" +				result=$? +				if ! echo "$line" | grep -Eq "^\[(ticket/[0-9]+|feature/$branch_regex|task/$branch_regex)\] [A-Z].+$" +				then +					# Don't be too strict. +					# Commits may be temporary, intended to be squashed later. +					# Just issue a warning here. +					complain "$severity: heading should be a sentence beginning with a capital letter." 1>&2 +					complain "You entered:" 1>&2 +					complain "$line" 1>&2 +				fi +				# restore exit code +				(exit $result)  			;;  			"empty")  				err=$ERR_EMPTY; @@ -128,11 +222,15 @@ do  				# Should not end up here  				false  			;; +			"possibly-eof") +				# Allow empty and/or comment lines at the end +				! tail -n +"$i" "$1" |grep -qvE '^($|#)' +			;;  			"comment")  				echo "$line" | grep -Eq "^#";  			;;  			*) -				echo "Unrecognised token $expect" >&2; +				complain "Unrecognised token $expect" >&2;  				quit $err;  			;;  		esac @@ -188,7 +286,7 @@ do  				in_description=1;  			;;  			"footer") -				expecting="footer eof"; +				expecting="footer possibly-eof";  				if [ "$tickets" = "" ]  				then  					tickets="$line"; @@ -199,8 +297,11 @@ do  			"comment")  				# Comments should expect the same thing again  			;; +			"possibly-eof") +				expecting="eof"; +			;;  			*) -				echo "Unrecognised token $expect" >&2; +				complain "Unrecognised token $expect" >&2;  				quit 254;  			;;  		esac @@ -214,11 +315,11 @@ do  	else  		# None of the expected line formats matched  		# Guess we'll call it a day here then -		echo "Syntax error on line $i:" >&2; -		echo ">> $line" >&2; -		echo -n "Expecting: " >&2; -		echo "$expecting" | sed 's/ /, /g' >&2; -		exit $err; +		complain "Syntax error on line $i:" >&2; +		complain ">> $line" >&2; +		complain -n "Expecting: " >&2; +		complain "$expecting" | sed 's/ /, /g' >&2; +		quit $err;  	fi  	i=$(( $i + 1 )); @@ -227,7 +328,7 @@ done  # If EOF is expected exit cleanly  echo "$expecting" | grep -q "eof" || (  	# Unexpected EOF, error -	echo "Unexpected EOF encountered" >&2; +	complain "Unexpected EOF encountered" >&2;  	quit $ERR_EOF;  ) && (  	# Do post scan checks @@ -238,8 +339,8 @@ echo "$expecting" | grep -q "eof" || (  		if [ ! -z "$dupes" ]  		then -			echo "The following tickets are repeated:" >&2; -			echo "$dupes" | sed 's/ /\n/g;s/^/* /g' >&2; +			complain "The following tickets are repeated:" >&2; +			complain "$dupes" | sed 's/ /\n/g;s/^/* /g' >&2;  			quit $ERR_FOOTER;  		fi  	fi @@ -247,8 +348,8 @@ echo "$expecting" | grep -q "eof" || (  	if [ $ticket -gt 0 ]  	then  		echo "$tickets" | grep -Eq "\bPHPBB3-$ticket\b" || ( -			echo "Ticket ID [$ticket] of branch missing from list of tickets:" >&2; -			echo "$tickets" | sed 's/ /\n/g;s/^/* /g' >&2; +			complain "Ticket ID [$ticket] of branch missing from list of tickets:" >&2; +			complain "$tickets" | sed 's/ /\n/g;s/^/* /g' >&2;  			quit $ERR_FOOTER;  		) || exit $?;  	fi diff --git a/git-tools/hooks/pre-commit b/git-tools/hooks/pre-commit index 4d03359773..03babe47cd 100755 --- a/git-tools/hooks/pre-commit +++ b/git-tools/hooks/pre-commit @@ -12,8 +12,17 @@  # ln -s ../../git-tools/hooks/pre-commit \\  #   .git/hooks/pre-commit -# NOTE: this is run through /usr/bin/env -PHP_BIN=php +if [ -z "$PHP_BIN" ] +then +	PHP_BIN=php +fi + +if [ "$(echo -e test)" = test ] +then +	echo_e="echo -e" +else +	echo_e="echo" +fi  # necessary check for initial commit  if git rev-parse --verify HEAD >/dev/null 2>&1 @@ -27,7 +36,7 @@ fi  error=0  errors="" -if ! which $PHP_BIN >/dev/null 2>&1 +if ! which "$PHP_BIN" >/dev/null 2>&1  then  	echo "PHP Syntax check failed:"  	echo "PHP binary does not exist or is not in path: $PHP_BIN" @@ -64,7 +73,13 @@ do  	# check the staged file content for syntax errors  	# using php -l (lint) -	result=$(git cat-file -p $sha | /usr/bin/env $PHP_BIN -l 2>/dev/null) +	# note: if display_errors=stderr in php.ini, +	# parse errors are printed on stderr; otherwise +	# they are printed on stdout. +	# we filter everything other than parse errors +	# with a grep below, therefore it should be safe +	# to combine stdout and stderr in all circumstances +	result=$(git cat-file -p $sha | "$PHP_BIN" -l 2>&1)  	if [ $? -ne 0 ]  	then  		error=1 @@ -76,7 +91,45 @@ unset IFS  if [ $error -eq 1 ]  then -	echo -e "PHP Syntax check failed:"; -	echo -e "$errors" | grep "^Parse error:" +	echo "PHP Syntax check failed:" +	# php "display errors" (display_errors php.ini value) +	# and "log errors" (log_errors php.ini value). +	# these are independent settings - see main/main.c in php source. +	# the "log errors" setting produces output which +	# starts with "PHP Parse error:"; the "display errors" +	# setting produces output starting with "Parse error:". +	# if both are turned on php dumps the parse error twice. +	# therefore here we try to grep for one version and +	# if that yields no results grep for the other version. +	# +	# other fun php facts: +	# +	# 1. in cli, display_errors and log_errors have different +	#    destinations by default. display_errors prints to +	#    standard output and log_errors prints to standard error. +	#    whether these destinations make sense is left +	#    as an exercise for the reader. +	# 2. as mentioned above, with all output turned on +	#    php will print parse errors twice, one time on stdout +	#    and one time on stderr. +	# 3. it is possible to set both display_errors and log_errors +	#    to off. if this is done php will print the text +	#    "Errors parsing <file>" but will not say what +	#    the errors are. useful behavior, this. +	# 4. on my system display_errors defaults to on and +	#    log_errors defaults to off, therefore providing +	#    by default one copy of messages. your mileage may vary. +	# 5. by setting display_errors=stderr and log_errors=on, +	#    both sets of messages will be printed on stderr. +	# 6. php-cgi binary, given display_errors=stderr and +	#    log_errors=on, still prints both sets of messages +	#    on stderr, but formats one set as an html fragment. +	# 7. your entry here? ;) +	$echo_e "$errors" | grep "^Parse error:" +	if [ $? -ne 0 ] +	then +		# match failed +		$echo_e "$errors" | grep "^PHP Parse error:" +	fi  	exit 1  fi diff --git a/git-tools/hooks/prepare-commit-msg b/git-tools/hooks/prepare-commit-msg index 2bf25e58a4..11d2b6b2f2 100755 --- a/git-tools/hooks/prepare-commit-msg +++ b/git-tools/hooks/prepare-commit-msg @@ -35,8 +35,8 @@ then  	# Branch is prefixed with 'ticket/', append ticket ID to message  	if [ "$branch" != "${branch##ticket/}" ];  	then -		tail="\n\nPHPBB3-${branch##ticket/}"; +		tail="$(printf "\n\nPHPBB3-${branch##ticket/}")";  	fi -	echo "[$branch]$tail $(cat "$1")" > "$1" +	echo "[$branch] $tail$(cat "$1")" > "$1"  fi diff --git a/git-tools/merge.php b/git-tools/merge.php new file mode 100755 index 0000000000..034bd2032c --- /dev/null +++ b/git-tools/merge.php @@ -0,0 +1,175 @@ +#!/usr/bin/env php +<?php +/** +* +* @package phpBB3 +* @copyright (c) 2011 phpBB Group +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2 +* +*/ + +function show_usage() +{ +	$filename = basename(__FILE__); + +	echo "$filename merges a github pull request.\n"; +	echo "\n"; + +	echo "Usage: [php] $filename -p pull_request_id [OPTIONS]\n"; +	echo "\n"; + +	echo "Options:\n"; +	echo " -p pull_request_id             The pull request id to be merged (mandatory)\n"; +	echo " -r remote                      Remote of upstream, defaults to 'upstream' (optional)\n"; +	echo " -d                             Outputs the commands instead of running them (optional)\n"; +	echo " -h                             This help text\n"; + +	exit(2); +} + +// Handle arguments +$opts = getopt('p:r:dh'); + +if (empty($opts) || isset($opts['h'])) +{ +	show_usage(); +} + +$pull_id	= get_arg($opts, 'p', ''); +$remote		= get_arg($opts, 'r', 'upstream'); +$dry_run	= !get_arg($opts, 'd', true); + +try +{ +	exit(work($pull_id, $remote)); +} +catch (RuntimeException $e) +{ +	echo $e->getMessage(); +	exit($e->getCode()); +} + +function work($pull_id, $remote) +{ +	// Get some basic data +	$pull = get_pull('phpbb', 'phpbb3', $pull_id); + +	if (!$pull_id) +	{ +		show_usage(); +	} + +	if ($pull['state'] != 'open') +	{ +		throw new RuntimeException(sprintf("Error: pull request is closed\n", +			$target_branch), 5); +	} + +	$pull_user = $pull['head'][0]; +	$pull_branch = $pull['head'][1]; +	$target_branch = $pull['base'][1]; + +	switch ($target_branch) +	{ +		case 'develop-olympus': +			run("git checkout develop-olympus"); +			run("git pull $remote develop-olympus"); + +			add_remote($pull_user, 'phpbb3'); +			run("git fetch $pull_user"); +			run("git merge --no-ff $pull_user/$pull_branch"); +			run("phpunit"); + +			run("git checkout develop"); +			run("git pull $remote develop"); +			run("git merge --no-ff develop-olympus"); +			run("phpunit"); +		break; + +		case 'develop': +			run("git checkout develop"); +			run("git pull $remote develop"); + +			add_remote($pull_user, 'phpbb3'); +			run("git fetch $pull_user"); +			run("git merge --no-ff $pull_user/$pull_branch"); +			run("phpunit"); +		break; + +		default: +			throw new RuntimeException(sprintf("Error: pull request target branch '%s' is not a main branch\n", +				$target_branch), 5); +		break; +	} +} + +function add_remote($username, $repository, $pushable = false) +{ +	$url = get_repository_url($username, $repository, false); +	run("git remote add $username $url", true); + +	if ($pushable) +	{ +		$ssh_url = get_repository_url($username, $repository, true); +		run("git remote set-url --push $username $ssh_url"); +	} +} + +function get_repository_url($username, $repository, $ssh = false) +{ +	$url_base = ($ssh) ? 'git@github.com:' : 'git://github.com/'; + +	return $url_base . $username . '/' . $repository . '.git'; +} + +function api_request($query) +{ +	$contents = file_get_contents("http://github.com/api/v2/json/$query"); + +	if ($contents === false) +	{ +		throw new RuntimeException("Error: failed to retrieve pull request data\n", 4); +	} + +	return json_decode($contents); +} + +function get_pull($username, $repository, $pull_id) +{ +	$request = api_request("pulls/$username/$repository/$pull_id"); + +	$pull = $request->pull; + +	$pull_data = array( +		'base'  => array($pull->base->user->login, $pull->base->ref), +		'head'  => array($pull->head->user->login, $pull->head->ref), +		'state' => $pull->state, +	); + +	return $pull_data; +} + +function get_arg($array, $index, $default) +{ +	return isset($array[$index]) ? $array[$index] : $default; +} + +function run($cmd, $ignore_fail = false) +{ +	global $dry_run; + +	if (!empty($dry_run)) +	{ +		echo "$cmd\n"; +	} +	else +	{ +		passthru(escapeshellcmd($cmd), $status); + +		if ($status != 0 && !$ignore_fail) +		{ +			throw new RuntimeException(sprintf("Error: command '%s' failed with status %s'\n", +				$cmd, $status), 6); +		} +	} +} diff --git a/git-tools/setup_github_network.php b/git-tools/setup_github_network.php new file mode 100755 index 0000000000..5f2e1609a7 --- /dev/null +++ b/git-tools/setup_github_network.php @@ -0,0 +1,248 @@ +#!/usr/bin/env php +<?php +/** +* +* @package phpBB3 +* @copyright (c) 2011 phpBB Group +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2 +* +*/ + +function show_usage() +{ +	$filename = basename(__FILE__); + +	echo "$filename adds repositories of a github network as remotes to a local git repository.\n"; +	echo "\n"; + +	echo "Usage: [php] $filename -s collaborators|organisation|contributors|network [OPTIONS]\n"; +	echo "\n"; + +	echo "Scopes:\n"; +	echo "  collaborators                 Repositories of people who have push access to the specified repository\n"; +	echo "  contributors                  Repositories of people who have contributed to the specified repository\n"; +	echo "  organisation                  Repositories of members of the organisation at github\n"; +	echo "  network                       All repositories of the whole github network\n"; +	echo "\n"; + +	echo "Options:\n"; +	echo " -s scope                       See description above (mandatory)\n"; +	echo " -u github_username             Overwrites the github username (optional)\n"; +	echo " -r repository_name             Overwrites the repository name (optional)\n"; +	echo " -m your_github_username        Sets up ssh:// instead of git:// for pushable repositories (optional)\n"; +	echo " -d                             Outputs the commands instead of running them (optional)\n"; +	echo " -h                             This help text\n"; + +	exit(1); +} + +// Handle arguments +$opts = getopt('s:u:r:m:dh'); + +if (empty($opts) || isset($opts['h'])) +{ +	show_usage(); +} + +$scope			= get_arg($opts, 's', ''); +$username		= get_arg($opts, 'u', 'phpbb'); +$repository 	= get_arg($opts, 'r', 'phpbb3'); +$developer		= get_arg($opts, 'm', ''); +$dry_run		= !get_arg($opts, 'd', true); +run(null, $dry_run); +exit(work($scope, $username, $repository, $developer)); + +function work($scope, $username, $repository, $developer) +{ +	// Get some basic data +	$network		= get_network($username, $repository); +	$collaborators	= get_collaborators($username, $repository); + +	if ($network === false || $collaborators === false) +	{ +		echo "Error: failed to retrieve network or collaborators\n"; +		return 1; +	} + +	switch ($scope) +	{ +		case 'collaborators': +			$remotes = array_intersect_key($network, $collaborators); +		break; + +		case 'organisation': +			$remotes = array_intersect_key($network, get_organisation_members($username)); +		break; + +		case 'contributors': +			$remotes = array_intersect_key($network, get_contributors($username, $repository)); +		break; + +		case 'network': +			$remotes = $network; +		break; + +		default: +			show_usage(); +	} + +	if (file_exists('.git')) +	{ +		add_remote($username, $repository, isset($collaborators[$developer])); +	} +	else +	{ +		clone_repository($username, $repository, isset($collaborators[$developer])); +	} + +	// Add private security repository for developers +	if ($username == 'phpbb' && $repository == 'phpbb3' && isset($collaborators[$developer])) +	{ +		run("git remote add $username-security " . get_repository_url($username, "$repository-security", true)); +	} + +	// Skip blessed repository. +	unset($remotes[$username]); + +	foreach ($remotes as $remote) +	{ +		add_remote($remote['username'], $remote['repository'], $remote['username'] == $developer); +	} + +	run('git remote update'); +} + +function clone_repository($username, $repository, $pushable = false) +{ +	$url = get_repository_url($username, $repository, false); +	run("git clone $url ./ --origin $username"); + +	if ($pushable) +	{ +		$ssh_url = get_repository_url($username, $repository, true); +		run("git remote set-url --push $username $ssh_url"); +	} +} + +function add_remote($username, $repository, $pushable = false) +{ +	$url = get_repository_url($username, $repository, false); +	run("git remote add $username $url"); + +	if ($pushable) +	{ +		$ssh_url = get_repository_url($username, $repository, true); +		run("git remote set-url --push $username $ssh_url"); +	} +} + +function get_repository_url($username, $repository, $ssh = false) +{ +	$url_base = ($ssh) ? 'git@github.com:' : 'git://github.com/'; + +	return $url_base . $username . '/' . $repository . '.git'; +} + +function api_request($query) +{ +	$contents = file_get_contents("http://github.com/api/v2/json/$query"); +	if ($contents === false) +	{ +		return false; +	} +	return json_decode($contents); +} + +function get_contributors($username, $repository) +{ +	$request = api_request("repos/show/$username/$repository/contributors"); +	if ($request === false) +	{ +		return false; +	} + +	$usernames = array(); +	foreach ($request->contributors as $contributor) +	{ +		$usernames[$contributor->login] = $contributor->login; +	} + +	return $usernames; +} + +function get_organisation_members($username) +{ +	$request = api_request("organizations/$username/public_members"); +	if ($request === false) +	{ +		return false; +	} + +	$usernames = array(); +	foreach ($request->users as $member) +	{ +		$usernames[$member->login] = $member->login; +	} + +	return $usernames; +} + +function get_collaborators($username, $repository) +{ +	$request = api_request("repos/show/$username/$repository/collaborators"); +	if ($request === false) +	{ +		return false; +	} + +	$usernames = array(); +	foreach ($request->collaborators as $collaborator) +	{ +		$usernames[$collaborator] = $collaborator; +	} + +	return $usernames; +} + +function get_network($username, $repository) +{ +	$request = api_request("repos/show/$username/$repository/network"); +	if ($request === false) +	{ +		return false; +	} + +	$usernames = array(); +	foreach ($request->network as $network) +	{ +		$usernames[$network->owner] = array( +			'username'		=> $network->owner, +			'repository'	=> $network->name, +		); +	} + +	return $usernames; +} + +function get_arg($array, $index, $default) +{ +	return isset($array[$index]) ? $array[$index] : $default; +} + +function run($cmd, $dry = false) +{ +	static $dry_run; + +	if (is_null($cmd)) +	{ +		$dry_run = $dry; +	} +	else if (!empty($dry_run)) +	{ +		echo "$cmd\n"; +	} +	else +	{ +		passthru(escapeshellcmd($cmd)); +	} +}  | 
