Changeset View
Changeset View
Standalone View
Standalone View
src/repository/api/ArcanistGitAPI.php
| Show First 20 Lines • Show All 789 Lines • ▼ Show 20 Lines | final class ArcanistGitAPI extends ArcanistRepositoryAPI { | ||||
| public function getChangedFiles($since_commit) { | public function getChangedFiles($since_commit) { | ||||
| list($stdout) = $this->execxLocal( | list($stdout) = $this->execxLocal( | ||||
| 'diff --raw %s', | 'diff --raw %s', | ||||
| $since_commit); | $since_commit); | ||||
| return $this->parseGitStatus($stdout); | return $this->parseGitStatus($stdout); | ||||
| } | } | ||||
| public function getBlame($path) { | public function getBlame($path) { | ||||
| // TODO: 'git blame' supports --porcelain and we should probably use it. | |||||
| list($stdout) = $this->execxLocal( | list($stdout) = $this->execxLocal( | ||||
| 'blame --date=iso -w -M %s -- %s', | 'blame --porcelain -w -M %s -- %s', | ||||
| $this->getBaseCommit(), | $this->getBaseCommit(), | ||||
| $path); | $path); | ||||
| // the --porcelain format prints at least one header line per source line, | |||||
| // then the source line prefixed by a tab character | |||||
| $blame_info = preg_split('/^\t.*\n/m', rtrim($stdout)); | |||||
| // commit info is not repeated in these headers, so cache it | |||||
| $revision_data = array(); | |||||
| $blame = array(); | $blame = array(); | ||||
| foreach (explode("\n", trim($stdout)) as $line) { | foreach ($blame_info as $line_info) { | ||||
| if (!strlen($line)) { | $revision = substr($line_info, 0, 40); | ||||
| continue; | $data = idx($revision_data, $revision, array()); | ||||
| } | |||||
| // lines predating a git repo's history are blamed to the oldest revision, | if (empty($data)) { | ||||
| // with the commit hash prepended by a ^. we shouldn't count these lines | $matches = array(); | ||||
| // as blaming to the oldest diff's unfortunate author | if (!preg_match('/^author (.*)$/m', $line_info, $matches)) { | ||||
| if ($line[0] == '^') { | throw new Exception( | ||||
| continue; | pht( | ||||
| 'Unexpected output from %s: no author for commit %s', | |||||
| 'git blame', | |||||
| $revision)); | |||||
| } | |||||
| $data['author'] = $matches[1]; | |||||
| $data['from_first_commit'] = preg_match('/^boundary$/m', $line_info); | |||||
| $revision_data[$revision] = $data; | |||||
| } | } | ||||
| $matches = null; | // Ignore lines predating the git repository (on a boundary commit) | ||||
| $ok = preg_match( | // rather than blaming them on the oldest diff's unfortunate author | ||||
| '/^([0-9a-f]+)[^(]+?[(](.*?) +\d\d\d\d-\d\d-\d\d/', | if (!$data['from_first_commit']) { | ||||
| $line, | $blame[] = array($data['author'], $revision); | ||||
| $matches); | |||||
| if (!$ok) { | |||||
| throw new Exception(pht("Bad blame? `%s'", $line)); | |||||
| } | } | ||||
| $revision = $matches[1]; | |||||
| $author = $matches[2]; | |||||
| $blame[] = array($author, $revision); | |||||
| } | } | ||||
| return $blame; | return $blame; | ||||
| } | } | ||||
| public function getOriginalFileData($path) { | public function getOriginalFileData($path) { | ||||
| return $this->getFileDataAtRevision($path, $this->getBaseCommit()); | return $this->getFileDataAtRevision($path, $this->getBaseCommit()); | ||||
| } | } | ||||
| ▲ Show 20 Lines • Show All 445 Lines • Show Last 20 Lines | |||||