1<?php 2 3/** 4 * Represents a parsed commit message. 5 */ 6final class ArcanistDifferentialCommitMessage extends Phobject { 7 8 private $rawCorpus; 9 private $revisionID; 10 private $fields = array(); 11 private $xactions = null; 12 13 private $gitSVNBaseRevision; 14 private $gitSVNBasePath; 15 private $gitSVNUUID; 16 17 public static function newFromRawCorpus($corpus) { 18 $obj = new ArcanistDifferentialCommitMessage(); 19 $obj->rawCorpus = $corpus; 20 $obj->revisionID = $obj->parseRevisionIDFromRawCorpus($corpus); 21 22 $pattern = '/^git-svn-id:\s*([^@]+)@(\d+)\s+(.*)$/m'; 23 $match = null; 24 if (preg_match($pattern, $corpus, $match)) { 25 $obj->gitSVNBaseRevision = $match[1].'@'.$match[2]; 26 $obj->gitSVNBasePath = $match[1]; 27 $obj->gitSVNUUID = $match[3]; 28 } 29 30 return $obj; 31 } 32 33 public function getRawCorpus() { 34 return $this->rawCorpus; 35 } 36 37 public function getRevisionID() { 38 return $this->revisionID; 39 } 40 41 public function getRevisionMonogram() { 42 if ($this->revisionID) { 43 return 'D'.$this->revisionID; 44 } 45 return null; 46 } 47 48 public function pullDataFromConduit( 49 ConduitClient $conduit, 50 $partial = false) { 51 52 $result = $conduit->callMethodSynchronous( 53 'differential.parsecommitmessage', 54 array( 55 'corpus' => $this->rawCorpus, 56 'partial' => $partial, 57 )); 58 59 $this->fields = $result['fields']; 60 61 // NOTE: This does not exist prior to late October 2017. 62 $this->xactions = idx($result, 'transactions'); 63 64 if (!empty($result['errors'])) { 65 throw new ArcanistDifferentialCommitMessageParserException( 66 $result['errors']); 67 } 68 69 return $this; 70 } 71 72 public function getFieldValue($key) { 73 if (array_key_exists($key, $this->fields)) { 74 return $this->fields[$key]; 75 } 76 return null; 77 } 78 79 public function setFieldValue($key, $value) { 80 $this->fields[$key] = $value; 81 return $this; 82 } 83 84 public function getFields() { 85 return $this->fields; 86 } 87 88 public function getGitSVNBaseRevision() { 89 return $this->gitSVNBaseRevision; 90 } 91 92 public function getGitSVNBasePath() { 93 return $this->gitSVNBasePath; 94 } 95 96 public function getGitSVNUUID() { 97 return $this->gitSVNUUID; 98 } 99 100 public function getChecksum() { 101 $fields = array_filter($this->fields); 102 ksort($fields); 103 $fields = json_encode($fields); 104 return md5($fields); 105 } 106 107 public function getTransactions() { 108 return $this->xactions; 109 } 110 111 /** 112 * Extract the revision ID from a commit message. 113 * 114 * @param string Raw commit message. 115 * @return int|null Revision ID, if the commit message contains one. 116 */ 117 private function parseRevisionIDFromRawCorpus($corpus) { 118 $match = null; 119 if (!preg_match('/^Differential Revision:\s*(.+)/im', $corpus, $match)) { 120 return null; 121 } 122 123 $revision_value = trim($match[1]); 124 $revision_pattern = '/^[dD]([1-9]\d*)\z/'; 125 126 // Accept a bare revision ID like "D123". 127 if (preg_match($revision_pattern, $revision_value, $match)) { 128 return (int)$match[1]; 129 } 130 131 // Otherwise, try to find a full URI. 132 $uri = new PhutilURI($revision_value); 133 $path = $uri->getPath(); 134 $path = trim($path, '/'); 135 if (preg_match($revision_pattern, $path, $match)) { 136 return (int)$match[1]; 137 } 138 139 throw new ArcanistUsageException( 140 pht( 141 'Invalid "Differential Revision" field in commit message. This field '. 142 'should have a revision identifier like "%s" or a Phabricator URI '. 143 'like "%s", but has "%s".', 144 'D123', 145 'https://phabricator.example.com/D123', 146 $revision_value)); 147 } 148 149} 150