 b12e92e6e2
			
		
	
	b12e92e6e2
	
	
	
		
			
			Summary: Depends on D19779. Ref T13216. The push logs currently record the "hostWait", which is roughly "locking + subprocess cost". We also record locking separately, so we can figure out "subprocess cost" alone by subtracting the lock costs. However, the subprocess (normally `git receive-pack`) runs hooks, and we don't have an easy way to figure out how much time was spent doing actual `git` stuff vs spent doing commit hook processing. This would have been useful in diagnosing at least one recent issue. Track at least a rough hook cost and record it in the push logs. Test Plan: Pushed to a repository, saw a reasonable hook cost appear in the database table. Reviewers: amckinley Reviewed By: amckinley Subscribers: PHID-OPKG-gm6ozazyms6q6i22gyam Maniphest Tasks: T13216 Differential Revision: https://secure.phabricator.com/D19780
		
			
				
	
	
		
			238 lines
		
	
	
		
			7.4 KiB
		
	
	
	
		
			PHP
		
	
	
		
			Executable File
		
	
	
	
	
			
		
		
	
	
			238 lines
		
	
	
		
			7.4 KiB
		
	
	
	
		
			PHP
		
	
	
		
			Executable File
		
	
	
	
	
| #!/usr/bin/env php
 | |
| <?php
 | |
| 
 | |
| // NOTE: This script will sometimes emit a warning like this on startup:
 | |
| //
 | |
| //   No entry for terminal type "unknown";
 | |
| //   using dumb terminal settings.
 | |
| //
 | |
| // This can be fixed by adding "TERM=dumb" to the shebang line, but doing so
 | |
| // causes some systems to hang mysteriously. See T7119.
 | |
| 
 | |
| // Commit hooks execute in an unusual context where the environment may be
 | |
| // unavailable, particularly in SVN. The first parameter to this script is
 | |
| // either a bare repository identifier ("X"), or a repository identifier
 | |
| // followed by an instance identifier ("X:instance"). If we have an instance
 | |
| // identifier, unpack it into the environment before we start up. This allows
 | |
| // subclasses of PhabricatorConfigSiteSource to read it and build an instance
 | |
| // environment.
 | |
| 
 | |
| $hook_start = microtime(true);
 | |
| 
 | |
| if ($argc > 1) {
 | |
|   $context = $argv[1];
 | |
|   $context = explode(':', $context, 2);
 | |
|   $argv[1] = $context[0];
 | |
| 
 | |
|   if (count($context) > 1) {
 | |
|     $_ENV['PHABRICATOR_INSTANCE'] = $context[1];
 | |
|     putenv('PHABRICATOR_INSTANCE='.$context[1]);
 | |
|   }
 | |
| }
 | |
| 
 | |
| $root = dirname(dirname(dirname(__FILE__)));
 | |
| require_once $root.'/scripts/__init_script__.php';
 | |
| 
 | |
| if ($argc < 2) {
 | |
|   throw new Exception(pht('usage: commit-hook <repository>'));
 | |
| }
 | |
| 
 | |
| $engine = id(new DiffusionCommitHookEngine())
 | |
|   ->setStartTime($hook_start);
 | |
| 
 | |
| $repository = id(new PhabricatorRepositoryQuery())
 | |
|   ->setViewer(PhabricatorUser::getOmnipotentUser())
 | |
|   ->withIdentifiers(array($argv[1]))
 | |
|   ->needProjectPHIDs(true)
 | |
|   ->executeOne();
 | |
| 
 | |
| if (!$repository) {
 | |
|   throw new Exception(pht('No such repository "%s"!', $argv[1]));
 | |
| }
 | |
| 
 | |
| if (!$repository->isHosted()) {
 | |
|   // In Mercurial, the "pretxnchangegroup" hook fires for both pulls and
 | |
|   // pushes. Normally we only install the hook for hosted repositories, but
 | |
|   // if a hosted repository is later converted into an observed repository we
 | |
|   // can end up with an observed repository that has the hook installed.
 | |
|   // If we're running hooks from an observed repository, just exit without
 | |
|   // taking action. For more discussion, see PHI24.
 | |
|   return 0;
 | |
| }
 | |
| 
 | |
| $engine->setRepository($repository);
 | |
| 
 | |
| $args = new PhutilArgumentParser($argv);
 | |
| $args->parsePartial(
 | |
|   array(
 | |
|     array(
 | |
|       'name' => 'hook-mode',
 | |
|       'param' => 'mode',
 | |
|       'help' => pht('Hook execution mode.'),
 | |
|     ),
 | |
|   ));
 | |
| 
 | |
| $argv = array_merge(
 | |
|   array($argv[0]),
 | |
|   $args->getUnconsumedArgumentVector());
 | |
| 
 | |
| // Figure out which user is writing the commit.
 | |
| $hook_mode = $args->getArg('hook-mode');
 | |
| if ($hook_mode !== null) {
 | |
|   $known_modes = array(
 | |
|     'svn-revprop' => true,
 | |
|   );
 | |
| 
 | |
|   if (empty($known_modes[$hook_mode])) {
 | |
|     throw new Exception(
 | |
|       pht(
 | |
|         'Invalid Hook Mode: This hook was invoked in "%s" mode, but this '.
 | |
|         'is not a recognized hook mode. Valid modes are: %s.',
 | |
|         $hook_mode,
 | |
|         implode(', ', array_keys($known_modes))));
 | |
|   }
 | |
| }
 | |
| 
 | |
| $is_svnrevprop = ($hook_mode == 'svn-revprop');
 | |
| 
 | |
| if ($is_svnrevprop) {
 | |
|   // For now, we let these through if the repository allows dangerous changes
 | |
|   // and prevent them if it doesn't. See T11208 for discussion.
 | |
| 
 | |
|   $revprop_key = $argv[5];
 | |
| 
 | |
|   if ($repository->shouldAllowDangerousChanges()) {
 | |
|     $err = 0;
 | |
|   } else {
 | |
|     $err = 1;
 | |
| 
 | |
|     $console = PhutilConsole::getConsole();
 | |
|     $console->writeErr(
 | |
|       pht(
 | |
|         "DANGEROUS CHANGE: Dangerous change protection is enabled for this ".
 | |
|         "repository, so you can not change revision properties (you are ".
 | |
|         "attempting to edit \"%s\").\n".
 | |
|         "Edit the repository configuration before making dangerous changes.",
 | |
|         $revprop_key));
 | |
|   }
 | |
| 
 | |
|   exit($err);
 | |
| } else if ($repository->isGit() || $repository->isHg()) {
 | |
|   $username = getenv(DiffusionCommitHookEngine::ENV_USER);
 | |
|   if (!strlen($username)) {
 | |
|     throw new Exception(
 | |
|       pht(
 | |
|         'No Direct Pushes: You are pushing directly to a repository hosted '.
 | |
|         'by Phabricator. This will not work. See "No Direct Pushes" in the '.
 | |
|         'documentation for more information.'));
 | |
|   }
 | |
| 
 | |
|   if ($repository->isHg()) {
 | |
|     // We respond to several different hooks in Mercurial.
 | |
|     $engine->setMercurialHook($argv[2]);
 | |
|   }
 | |
| 
 | |
| } else if ($repository->isSVN()) {
 | |
|   // NOTE: In Subversion, the entire environment gets wiped so we can't read
 | |
|   // DiffusionCommitHookEngine::ENV_USER. Instead, we've set "--tunnel-user" to
 | |
|   // specify the correct user; read this user out of the commit log.
 | |
| 
 | |
|   if ($argc < 4) {
 | |
|     throw new Exception(pht('usage: commit-hook <repository> <repo> <txn>'));
 | |
|   }
 | |
| 
 | |
|   $svn_repo = $argv[2];
 | |
|   $svn_txn = $argv[3];
 | |
|   list($username) = execx('svnlook author -t %s %s', $svn_txn, $svn_repo);
 | |
|   $username = rtrim($username, "\n");
 | |
| 
 | |
|   $engine->setSubversionTransactionInfo($svn_txn, $svn_repo);
 | |
| } else {
 | |
|   throw new Exception(pht('Unknown repository type.'));
 | |
| }
 | |
| 
 | |
| $user = id(new PhabricatorPeopleQuery())
 | |
|   ->setViewer(PhabricatorUser::getOmnipotentUser())
 | |
|   ->withUsernames(array($username))
 | |
|   ->executeOne();
 | |
| 
 | |
| if (!$user) {
 | |
|   throw new Exception(pht('No such user "%s"!', $username));
 | |
| }
 | |
| 
 | |
| $engine->setViewer($user);
 | |
| 
 | |
| 
 | |
| // Read stdin for the hook engine.
 | |
| 
 | |
| if ($repository->isHg()) {
 | |
|   // Mercurial leaves stdin open, so we can't just read it until EOF.
 | |
|   $stdin = '';
 | |
| } else {
 | |
|   // Git and Subversion write data into stdin and then close it. Read the
 | |
|   // data.
 | |
|   $stdin = @file_get_contents('php://stdin');
 | |
|   if ($stdin === false) {
 | |
|     throw new Exception(pht('Failed to read stdin!'));
 | |
|   }
 | |
| }
 | |
| 
 | |
| $engine->setStdin($stdin);
 | |
| $engine->setOriginalArgv(array_slice($argv, 2));
 | |
| 
 | |
| $remote_address = getenv(DiffusionCommitHookEngine::ENV_REMOTE_ADDRESS);
 | |
| if (strlen($remote_address)) {
 | |
|   $engine->setRemoteAddress($remote_address);
 | |
| }
 | |
| 
 | |
| $remote_protocol = getenv(DiffusionCommitHookEngine::ENV_REMOTE_PROTOCOL);
 | |
| if (strlen($remote_protocol)) {
 | |
|   $engine->setRemoteProtocol($remote_protocol);
 | |
| }
 | |
| 
 | |
| $request_identifier = getenv(DiffusionCommitHookEngine::ENV_REQUEST);
 | |
| if (strlen($request_identifier)) {
 | |
|   $engine->setRequestIdentifier($request_identifier);
 | |
| }
 | |
| 
 | |
| try {
 | |
|   $err = $engine->execute();
 | |
| } catch (DiffusionCommitHookRejectException $ex) {
 | |
|   $console = PhutilConsole::getConsole();
 | |
| 
 | |
|   if (PhabricatorEnv::getEnvConfig('phabricator.serious-business')) {
 | |
|     $preamble = pht('*** PUSH REJECTED BY COMMIT HOOK ***');
 | |
|   } else {
 | |
|     $preamble = pht(<<<EOTXT
 | |
| +---------------------------------------------------------------+
 | |
| |      * * * PUSH REJECTED BY EVIL DRAGON BUREAUCRATS * * *     |
 | |
| +---------------------------------------------------------------+
 | |
|              \
 | |
|               \                    ^    /^
 | |
|                \                  / \  // \
 | |
|                 \   |\___/|      /   \//  .\
 | |
|                  \  /V  V  \__  /    //  | \ \           *----*
 | |
|                    /     /  \/_/    //   |  \  \          \   |
 | |
|                    @___@`    \/_   //    |   \   \         \/\ \
 | |
|                   0/0/|       \/_ //     |    \    \         \  \
 | |
|               0/0/0/0/|        \///      |     \     \       |  |
 | |
|            0/0/0/0/0/_|_ /   (  //       |      \     _\     |  /
 | |
|         0/0/0/0/0/0/`/,_ _ _/  ) ; -.    |    _ _\.-~       /   /
 | |
|                     ,-}        _      *-.|.-~-.           .~    ~
 | |
|   *     \__/         `/\      /                 ~-. _ .-~      /
 | |
|    \____(Oo)            *.   }            {                   /
 | |
|    (    (..)           .----~-.\        \-`                 .~
 | |
|    //___\\\\  \ DENIED!  ///.----..<        \             _ -~
 | |
|   //     \\\\                ///-._ _ _ _ _ _ _{^ - - - - ~
 | |
| 
 | |
| EOTXT
 | |
| );
 | |
|   }
 | |
| 
 | |
|   $console->writeErr("%s\n\n", $preamble);
 | |
|   $console->writeErr("%s\n\n", $ex->getMessage());
 | |
|   $err = 1;
 | |
| }
 | |
| 
 | |
| exit($err);
 |