Path: blob/master/src/applications/diffusion/query/lowlevel/DiffusionLowLevelCommitQuery.php
12242 views
<?php12/**3* Populate a @{class:DiffusionCommitRef} with information about a specific4* commit in a repository. This is a low-level query which talks directly to5* the underlying VCS.6*/7final class DiffusionLowLevelCommitQuery8extends DiffusionLowLevelQuery {910private $identifier;1112public function withIdentifier($identifier) {13$this->identifier = $identifier;14return $this;15}1617protected function executeQuery() {18if (!strlen($this->identifier)) {19throw new PhutilInvalidStateException('withIdentifier');20}2122$type = $this->getRepository()->getVersionControlSystem();23switch ($type) {24case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT:25$result = $this->loadGitCommitRef();26break;27case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL:28$result = $this->loadMercurialCommitRef();29break;30case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN:31$result = $this->loadSubversionCommitRef();32break;33default:34throw new Exception(pht('Unsupported repository type "%s"!', $type));35}3637return $result;38}3940private function loadGitCommitRef() {41$repository = $this->getRepository();4243// See T5028. The "%B" (raw body) mode is not present in very old versions44// of Git. Use "%s" and "%b" ("subject" and "wrapped body") as an45// approximation.4647$git_binary = PhutilBinaryAnalyzer::getForBinary('git');48$git_version = $git_binary->getBinaryVersion();49if (version_compare($git_version, '1.7.2', '>=')) {50$body_format = '%B';51$split_body = false;52} else {53$body_format = '%s%x00%b';54$split_body = true;55}5657$argv = array();5859$argv[] = '-n';60$argv[] = '1';6162$argv[] = '--encoding=UTF-8';6364$argv[] = sprintf(65'--format=%s',66implode(67'%x00',68array(69'%e',70'%cn',71'%ce',72'%an',73'%ae',74'%T',75'%at',76$body_format,7778// The "git log" output includes a trailing newline. We want to79// faithfully capture only the exact text of the commit message,80// so include an explicit terminator: this makes sure the exact81// body text is surrounded by "\0" characters.82'~',83)));8485// Even though we pass --encoding here, git doesn't always succeed, so86// we try a little harder, since git *does* tell us what the actual encoding87// is correctly (unless it doesn't; encoding is sometimes empty).88list($info) = $repository->execxLocalCommand(89'log -n 1 %Ls %s --',90$argv,91gitsprintf('%s', $this->identifier));9293$parts = explode("\0", $info);94$encoding = array_shift($parts);9596foreach ($parts as $key => $part) {97if ($encoding) {98$part = phutil_utf8_convert($part, 'UTF-8', $encoding);99}100$parts[$key] = phutil_utf8ize($part);101if (!strlen($parts[$key])) {102$parts[$key] = null;103}104}105106$hashes = array(107id(new DiffusionCommitHash())108->setHashType(ArcanistDifferentialRevisionHash::HASH_GIT_COMMIT)109->setHashValue($this->identifier),110id(new DiffusionCommitHash())111->setHashType(ArcanistDifferentialRevisionHash::HASH_GIT_TREE)112->setHashValue($parts[4]),113);114115$author_epoch = (int)$parts[5];116if (!$author_epoch) {117$author_epoch = null;118}119120if ($split_body) {121// Here, the body is: "subject", "\0", "wrapped body". Stitch the122// pieces back together by putting a newline between them if both123// parts are nonempty.124125$head = $parts[6];126$tail = $parts[7];127128if (strlen($head) && strlen($tail)) {129$body = $head."\n\n".$tail;130} else if (strlen($head)) {131$body = $head;132} else if (strlen($tail)) {133$body = $tail;134} else {135$body = '';136}137} else {138// Here, the body is the raw unwrapped body.139$body = $parts[6];140}141142return id(new DiffusionCommitRef())143->setCommitterName($parts[0])144->setCommitterEmail($parts[1])145->setAuthorName($parts[2])146->setAuthorEmail($parts[3])147->setHashes($hashes)148->setAuthorEpoch($author_epoch)149->setMessage($body);150}151152private function loadMercurialCommitRef() {153$repository = $this->getRepository();154155list($stdout) = $repository->execxLocalCommand(156'log --template %s --rev %s',157'{author}\\n{desc}',158hgsprintf('%s', $this->identifier));159160list($author, $message) = explode("\n", $stdout, 2);161162$author = phutil_utf8ize($author);163$message = phutil_utf8ize($message);164165list($author_name, $author_email) = $this->splitUserIdentifier($author);166167$hashes = array(168id(new DiffusionCommitHash())169->setHashType(ArcanistDifferentialRevisionHash::HASH_MERCURIAL_COMMIT)170->setHashValue($this->identifier),171);172173return id(new DiffusionCommitRef())174->setAuthorName($author_name)175->setAuthorEmail($author_email)176->setMessage($message)177->setHashes($hashes);178}179180private function loadSubversionCommitRef() {181$repository = $this->getRepository();182183list($xml) = $repository->execxRemoteCommand(184'log --xml --limit 1 %s',185$repository->getSubversionPathURI(null, $this->identifier));186187// Subversion may send us back commit messages which won't parse because188// they have non UTF-8 garbage in them. Slam them into valid UTF-8.189$xml = phutil_utf8ize($xml);190$log = new SimpleXMLElement($xml);191$entry = $log->logentry[0];192193$author = (string)$entry->author;194$message = (string)$entry->msg;195196list($author_name, $author_email) = $this->splitUserIdentifier($author);197198// No hashes in Subversion.199$hashes = array();200201return id(new DiffusionCommitRef())202->setAuthorName($author_name)203->setAuthorEmail($author_email)204->setMessage($message)205->setHashes($hashes);206}207208private function splitUserIdentifier($user) {209$email = new PhutilEmailAddress($user);210211if ($email->getDisplayName() || $email->getDomainName()) {212$user_name = $email->getDisplayName();213$user_email = $email->getAddress();214} else {215$user_name = $email->getAddress();216$user_email = null;217}218219return array($user_name, $user_email);220}221222}223224225