CommitMessage.php 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351
  1. <?php
  2. namespace Elgg;
  3. use UnexpectedValueException;
  4. /**
  5. * Provides a structured format for parsing and examining our commit messages.
  6. *
  7. * @package Elgg.Core
  8. * @since 1.9
  9. *
  10. * @access private
  11. */
  12. class CommitMessage {
  13. /**
  14. * Valid parts of the message
  15. * The index is the index of the $matches array for regex
  16. * @var array
  17. */
  18. private $validMsgParts = array(
  19. 1 => 'type',
  20. 2 => 'component',
  21. 3 => 'summary',
  22. 5 => 'body'
  23. );
  24. /**
  25. * Message type
  26. *
  27. * @var string
  28. */
  29. private $type;
  30. /**
  31. * Message component
  32. *
  33. * @var string
  34. */
  35. private $component;
  36. /**
  37. * Message summary
  38. *
  39. * @var string
  40. */
  41. private $summary;
  42. /**
  43. * Optional message body
  44. *
  45. * @var string
  46. */
  47. private $body;
  48. /**
  49. * Original message text
  50. *
  51. * @var string
  52. */
  53. private $originalMsg = '';
  54. /**
  55. * Modified message text.
  56. *
  57. * @var string
  58. */
  59. private $msg = '';
  60. /**
  61. * An array of lines over the valid length
  62. *
  63. * @var array
  64. */
  65. private $lengthyLines;
  66. /**
  67. * Valid types
  68. *
  69. * @var array
  70. */
  71. private static $validTypes = array(
  72. 'feature',
  73. 'fix',
  74. 'docs',
  75. 'chore',
  76. 'perf',
  77. 'security',
  78. 'deprecate',
  79. );
  80. /**
  81. * Valid components
  82. *
  83. * @todo Not checked yet.
  84. *
  85. * @var array
  86. */
  87. private $validComponents = array(
  88. 'i18n',
  89. 'seo',
  90. 'a11y',
  91. 'cache',
  92. 'db',
  93. 'views',
  94. 'session',
  95. 'router'
  96. );
  97. /**
  98. * Ignore messages that match this regex
  99. *
  100. * @var string
  101. */
  102. private $ignoreRegex = '/^Merge |^Revert /i';
  103. /**
  104. * Regex to extract the message parts
  105. *
  106. * type(component): message
  107. * with an optional body following
  108. *
  109. * $matches = array(
  110. * 0 => everything
  111. * 1 => type
  112. * 2 => component
  113. * 3 => summary
  114. * 4 => body (with leading \ns)
  115. * 5 => body (without leading \ns)
  116. * )
  117. *
  118. * @var string
  119. */
  120. private $formatRegex = "/^(\w*)\(([\w]+)\)\: ([^\n]*)(\n\n?(.*))?$/is";
  121. /**
  122. * Max length of any line
  123. * @var int
  124. */
  125. private $maxLineLength = 160;
  126. /**
  127. * Checks if a commit message is in the correct format
  128. *
  129. * @param string|null $msg The commit message
  130. */
  131. public function __construct($msg = null) {
  132. if ($msg) {
  133. $this->setMsg($msg);
  134. }
  135. }
  136. /**
  137. * Sets the active message
  138. *
  139. * @param string $msg The message content
  140. *
  141. * @return void
  142. */
  143. public function setMsg($msg) {
  144. $this->originalMsg = $msg;
  145. $msg = str_replace(array("\r", "\n"), "\n", $msg);
  146. $this->msg = $this->removeComments($msg);
  147. $this->processMsg();
  148. }
  149. /**
  150. * Return the processed message.
  151. *
  152. * @return string
  153. */
  154. public function getMsg() {
  155. return $this->msg;
  156. }
  157. /**
  158. * Return the original message
  159. *
  160. * @return string
  161. */
  162. public function getOriginalMsg() {
  163. return $this->originalMsg;
  164. }
  165. /**
  166. * Should this msg be ignored for formatting?
  167. *
  168. * @return boolean
  169. */
  170. public function shouldIgnore() {
  171. return preg_match($this->ignoreRegex, $this->msg) === 1;
  172. }
  173. /**
  174. * Process the msg into its parts
  175. *
  176. * @return array
  177. */
  178. private function processMsg() {
  179. $matches = array();
  180. preg_match($this->formatRegex, $this->msg, $matches);
  181. foreach ($this->validMsgParts as $i => $part) {
  182. $this->$part = isset($matches[$i]) ? $matches[$i] : '';
  183. }
  184. $this->lengthyLines = $this->findLengthyLines($this->msg, $this->maxLineLength);
  185. }
  186. /**
  187. * Are all parts of the message valid
  188. *
  189. * @return bool
  190. */
  191. public function isValid() {
  192. return $this->isValidFormat() &&
  193. $this->isValidLineLength() &&
  194. $this->isValidType();
  195. }
  196. /**
  197. * Whether the message format conforms to our standards.
  198. *
  199. * @return boolean
  200. */
  201. public function isValidFormat() {
  202. return preg_match($this->formatRegex, $this->msg) === 1;
  203. }
  204. /**
  205. * Are any of the lines too long?
  206. *
  207. * @see getLengthyLines() to get line numbers
  208. *
  209. * @return bool
  210. */
  211. public function isValidLineLength() {
  212. return count($this->lengthyLines) === 0;
  213. }
  214. /**
  215. * Get the line number of lines that are too long
  216. *
  217. * @return array
  218. */
  219. public function getLengthyLines() {
  220. return $this->lengthyLines;
  221. }
  222. /**
  223. * Is the type valid
  224. *
  225. * @return boolean
  226. */
  227. public function isValidType() {
  228. return in_array($this->type, self::$validTypes);
  229. }
  230. /**
  231. * Return all valid types
  232. *
  233. * @return array
  234. */
  235. public static function getValidTypes() {
  236. return self::$validTypes;
  237. }
  238. /**
  239. * Return the max line length
  240. *
  241. * @return int
  242. */
  243. public function getMaxLineLength() {
  244. return $this->maxLineLength;
  245. }
  246. /**
  247. * Sets the max line length allowed.
  248. * Defaults to 160.
  249. *
  250. * @param int $len The maximum length.
  251. *
  252. * @return void
  253. */
  254. public function setMaxLineLength($len) {
  255. $this->maxLineLength = (int)$len;
  256. }
  257. /**
  258. * Get part of the message
  259. *
  260. * @param string $part One section of the message.
  261. *
  262. * @return string
  263. * @throws UnexpectedValueException
  264. */
  265. public function getPart($part) {
  266. if ($part && in_array($part, $this->validMsgParts)) {
  267. return $this->$part;
  268. }
  269. throw new UnexpectedValueException("`$part` not a valid message part.");
  270. }
  271. /**
  272. * Removes all lines that start with #
  273. *
  274. * @param string $msg The msg body of the commit
  275. *
  276. * @return string
  277. */
  278. public static function removeComments($msg) {
  279. $msg_arr = array();
  280. foreach (explode("\n", rtrim($msg)) as $line) {
  281. if (substr($line, 0, 1) !== '#') {
  282. $msg_arr[] = $line;
  283. }
  284. }
  285. return implode("\n", $msg_arr);
  286. }
  287. /**
  288. * Returns an array of line numbers > $max_len
  289. *
  290. * @param string $msg The content to parse
  291. * @param int $max_len Maximum length between \n in the $msg
  292. *
  293. * @return array
  294. */
  295. public static function findLengthyLines($msg, $max_len) {
  296. $lines = explode("\n", $msg);
  297. $lengthy_lines = array();
  298. foreach ($lines as $i => $line) {
  299. if (strlen($line) > $max_len) {
  300. $lengthy_lines[] = ++$i;
  301. }
  302. }
  303. return $lengthy_lines;
  304. }
  305. /** @inheritDoc */
  306. public function __toString() {
  307. return $this->getMsg();
  308. }
  309. }