validate_commit_msg.php 3.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152
  1. #!/usr/bin/php
  2. <?php
  3. /**
  4. * Validates the text of a commit message.
  5. *
  6. * Text should be passed as the only argument, the path to a file (compatibility
  7. * with commit-msg git hook), or through stdin.
  8. *
  9. * Writes any errors to stdout.
  10. * Exits with 0 on success, > = 0 on failure.
  11. *
  12. * Can't pass multiple msgs at once.
  13. *
  14. * To use as a git commit hook, make sure the PHP path is correct, then
  15. * copy or symlink to .git/hooks/commit-msg.
  16. *
  17. */
  18. $rootDir = dirname(__DIR__);
  19. require_once "$rootDir/vendor/autoload.php";
  20. $is_file = false;
  21. if ($argc === 2) {
  22. // check file or msg itself
  23. $arg = $argv[1];
  24. if (file_exists($arg)) {
  25. $is_file = true;
  26. $msg_tmp = file_get_contents($arg);
  27. } else {
  28. $msg_tmp = $arg;
  29. }
  30. } else {
  31. // check for std in
  32. $msg_tmp = file_get_contents("php://stdin");
  33. }
  34. $msg = new Elgg\CommitMessage($msg_tmp);
  35. if (!$msg->getMsg()) {
  36. usage();
  37. }
  38. if ($msg->shouldIgnore()) {
  39. output("Ignoring commit.", 'notice');
  40. exit(0);
  41. }
  42. // basic format
  43. // don't continue if not correct
  44. if (!$msg->isValidFormat()) {
  45. output("Fail.", 'error');
  46. output("Not in the format `type(component): summary`", 'error');
  47. output($msg, 'error');
  48. if ($is_file) {
  49. output("\nCommit message saved in " . $argv[1]);
  50. }
  51. exit(1);
  52. }
  53. $errors = array();
  54. // line lengths
  55. if (!$msg->isValidLineLength()) {
  56. $max = $msg->getMaxLineLength();
  57. foreach ($msg->getLengthyLines() as $line_num) {
  58. $errors[] = "Longer than $max characters at line $line_num";
  59. }
  60. }
  61. // type
  62. if (!$msg->isValidType()) {
  63. $errors[] = "Invalid type at line 1: `{$msg->getPart('type')}`. Not one of "
  64. . implode(', ', $msg->getValidTypes()) . '.';
  65. }
  66. // component
  67. // @todo only checking for existence right now via regex
  68. // @todo check for fixes, refs, etc only in body and not in summary?
  69. // @todo check for correct syntax for breaks and deprecates?
  70. if ($errors) {
  71. output('Fail', 'error');
  72. foreach ($errors as $error) {
  73. output($error, 'error');
  74. }
  75. $arg = escapeshellarg($msg);
  76. $cmd = "printf '%s' $arg | nl -ba";
  77. $output = shell_exec($cmd);
  78. output($output, 'error', false);
  79. if ($is_file) {
  80. output("\nCommit message saved in " . $argv[1]);
  81. }
  82. exit(1);
  83. } else {
  84. // only if we're not in a git commit
  85. if (!$is_file) {
  86. output('Ok', 'success');
  87. }
  88. exit(0);
  89. }
  90. /**
  91. * Output a msg followed by a \n
  92. *
  93. * @param string $msg The message to output
  94. * @param bool $error If true, the message is in red.
  95. */
  96. function output($msg, $type = 'message', $trailing_return = true) {
  97. $colors = array(
  98. 'red' => '0;31',
  99. 'green' => '0;32',
  100. 'yellow' => '0;33'
  101. );
  102. $types = array(
  103. 'message' => '',
  104. 'error' => 'red',
  105. 'notice' => 'yellow',
  106. 'success' => 'green'
  107. );
  108. $n = $trailing_return ? "\n" : '';
  109. switch ($type) {
  110. case 'error':
  111. case 'notice':
  112. case 'success':
  113. $color = $colors[$types[$type]];
  114. echo "\033[{$color}m$msg\033[0m{$n}";
  115. break;
  116. case 'message':
  117. default;
  118. echo "$msg{$n}";
  119. break;
  120. }
  121. }
  122. /**
  123. * Print usage and exit with error.
  124. */
  125. function usage() {
  126. output("Pass a commit message text or a file containing the text of a commit message as the only argument.");
  127. exit(1);
  128. }