Changeset View
Changeset View
Standalone View
Standalone View
src/repository/api/ArcanistGitAPI.php
| Show First 20 Lines • Show All 82 Lines • ▼ Show 20 Lines | final class ArcanistGitAPI extends ArcanistRepositoryAPI { | ||||
| * Tests if a child commit is descendant of a parent commit. | * Tests if a child commit is descendant of a parent commit. | ||||
| * If child and parent are the same, it returns false. | * If child and parent are the same, it returns false. | ||||
| * @param Child commit SHA. | * @param Child commit SHA. | ||||
| * @param Parent commit SHA. | * @param Parent commit SHA. | ||||
| * @return bool True if the child is a descendant of the parent. | * @return bool True if the child is a descendant of the parent. | ||||
| */ | */ | ||||
| private function isDescendant($child, $parent) { | private function isDescendant($child, $parent) { | ||||
| list($common_ancestor) = $this->execxLocal( | list($common_ancestor) = $this->execxLocal( | ||||
| 'merge-base %s %s', | 'merge-base -- %s %s', | ||||
| $child, | $child, | ||||
| $parent); | $parent); | ||||
| $common_ancestor = trim($common_ancestor); | $common_ancestor = trim($common_ancestor); | ||||
| return ($common_ancestor == $parent) && ($common_ancestor != $child); | return ($common_ancestor == $parent) && ($common_ancestor != $child); | ||||
| } | } | ||||
| public function getLocalCommitInformation() { | public function getLocalCommitInformation() { | ||||
| ▲ Show 20 Lines • Show All 109 Lines • ▼ Show 20 Lines | protected function buildBaseCommit($symbolic_commit) { | ||||
| if ($symbolic_commit !== null) { | if ($symbolic_commit !== null) { | ||||
| if ($symbolic_commit == self::GIT_MAGIC_ROOT_COMMIT) { | if ($symbolic_commit == self::GIT_MAGIC_ROOT_COMMIT) { | ||||
| $this->setBaseCommitExplanation( | $this->setBaseCommitExplanation( | ||||
| pht('you explicitly specified the empty tree.')); | pht('you explicitly specified the empty tree.')); | ||||
| return $symbolic_commit; | return $symbolic_commit; | ||||
| } | } | ||||
| list($err, $merge_base) = $this->execManualLocal( | list($err, $merge_base) = $this->execManualLocal( | ||||
| 'merge-base %s %s', | 'merge-base -- %s %s', | ||||
| $symbolic_commit, | $symbolic_commit, | ||||
| $this->getHeadCommit()); | $this->getHeadCommit()); | ||||
| if ($err) { | if ($err) { | ||||
| throw new ArcanistUsageException( | throw new ArcanistUsageException( | ||||
| pht( | pht( | ||||
| "Unable to find any git commit named '%s' in this repository.", | "Unable to find any git commit named '%s' in this repository.", | ||||
| $symbolic_commit)); | $symbolic_commit)); | ||||
| } | } | ||||
| ▲ Show 20 Lines • Show All 150 Lines • ▼ Show 20 Lines | if ($do_write) { | ||||
| $this->writeScratchFile('default-relative-commit', $default_relative); | $this->writeScratchFile('default-relative-commit', $default_relative); | ||||
| $this->setBaseCommitExplanation( | $this->setBaseCommitExplanation( | ||||
| pht( | pht( | ||||
| "it is the merge-base of '%s' and HEAD, as you just specified.", | "it is the merge-base of '%s' and HEAD, as you just specified.", | ||||
| $default_relative)); | $default_relative)); | ||||
| } | } | ||||
| list($merge_base) = $this->execxLocal( | list($merge_base) = $this->execxLocal( | ||||
| 'merge-base %s HEAD', | 'merge-base -- %s HEAD', | ||||
| $default_relative); | $default_relative); | ||||
| return trim($merge_base); | return trim($merge_base); | ||||
| } | } | ||||
| public function getHeadCommit() { | public function getHeadCommit() { | ||||
| if ($this->resolvedHeadCommit === null) { | if ($this->resolvedHeadCommit === null) { | ||||
| $this->resolvedHeadCommit = $this->resolveCommit( | $this->resolvedHeadCommit = $this->resolveCommit( | ||||
| ▲ Show 20 Lines • Show All 171 Lines • ▼ Show 20 Lines | final class ArcanistGitAPI extends ArcanistRepositoryAPI { | ||||
| public function getGitCommitLog() { | public function getGitCommitLog() { | ||||
| $relative = $this->getBaseCommit(); | $relative = $this->getBaseCommit(); | ||||
| if ($this->repositoryHasNoCommits) { | if ($this->repositoryHasNoCommits) { | ||||
| // No commits yet. | // No commits yet. | ||||
| return ''; | return ''; | ||||
| } else if ($relative == self::GIT_MAGIC_ROOT_COMMIT) { | } else if ($relative == self::GIT_MAGIC_ROOT_COMMIT) { | ||||
| // First commit. | // First commit. | ||||
| list($stdout) = $this->execxLocal( | list($stdout) = $this->execxLocal( | ||||
| 'log --format=medium HEAD'); | 'log --format=medium HEAD --'); | ||||
| } else { | } else { | ||||
| // 2..N commits. | // 2..N commits. | ||||
| list($stdout) = $this->execxLocal( | list($stdout) = $this->execxLocal( | ||||
| 'log --first-parent --format=medium %s..%s', | 'log --first-parent --format=medium %s --', | ||||
| gitsprintf( | |||||
| '%s..%s', | |||||
| $this->getBaseCommit(), | $this->getBaseCommit(), | ||||
| $this->getHeadCommit()); | $this->getHeadCommit())); | ||||
| } | } | ||||
| return $stdout; | return $stdout; | ||||
| } | } | ||||
| public function getGitHistoryLog() { | public function getGitHistoryLog() { | ||||
| list($stdout) = $this->execxLocal( | list($stdout) = $this->execxLocal( | ||||
| 'log --format=medium -n%d %s', | 'log --format=medium -n%d %s --', | ||||
| self::SEARCH_LENGTH_FOR_PARENT_REVISIONS, | self::SEARCH_LENGTH_FOR_PARENT_REVISIONS, | ||||
| $this->getBaseCommit()); | gitsprintf('%s', $this->getBaseCommit())); | ||||
| return $stdout; | return $stdout; | ||||
| } | } | ||||
| public function getSourceControlBaseRevision() { | public function getSourceControlBaseRevision() { | ||||
| list($stdout) = $this->execxLocal( | list($stdout) = $this->execxLocal( | ||||
| 'rev-parse %s', | 'rev-parse %s', | ||||
| $this->getBaseCommit()); | $this->getBaseCommit()); | ||||
| return rtrim($stdout, "\n"); | return rtrim($stdout, "\n"); | ||||
| ▲ Show 20 Lines • Show All 120 Lines • ▼ Show 20 Lines | if ($this->repositoryHasNoCommits) { | ||||
| $diff_base = 'HEAD'; | $diff_base = 'HEAD'; | ||||
| } | } | ||||
| // Find uncommitted changes. | // Find uncommitted changes. | ||||
| $uncommitted_future = $this->buildLocalFuture( | $uncommitted_future = $this->buildLocalFuture( | ||||
| array( | array( | ||||
| 'diff %C --raw %s --', | 'diff %C --raw %s --', | ||||
| $diff_options, | $diff_options, | ||||
| $diff_base, | gitsprintf('%s', $diff_base), | ||||
| )); | )); | ||||
| $untracked_future = $this->buildLocalFuture( | $untracked_future = $this->buildLocalFuture( | ||||
| array( | array( | ||||
| 'ls-files --others --exclude-standard', | 'ls-files --others --exclude-standard', | ||||
| )); | )); | ||||
| // Unstaged changes | // Unstaged changes | ||||
| ▲ Show 20 Lines • Show All 44 Lines • ▼ Show 20 Lines | protected function buildUncommittedStatus() { | ||||
| return $result->toArray(); | return $result->toArray(); | ||||
| } | } | ||||
| protected function buildCommitRangeStatus() { | protected function buildCommitRangeStatus() { | ||||
| list($stdout, $stderr) = $this->execxLocal( | list($stdout, $stderr) = $this->execxLocal( | ||||
| 'diff %C --raw %s HEAD --', | 'diff %C --raw %s HEAD --', | ||||
| $this->getDiffBaseOptions(), | $this->getDiffBaseOptions(), | ||||
| $this->getBaseCommit()); | gitsprintf('%s', $this->getBaseCommit())); | ||||
| return $this->parseGitRawDiff($stdout); | return $this->parseGitRawDiff($stdout); | ||||
| } | } | ||||
| public function getGitConfig($key, $default = null) { | public function getGitConfig($key, $default = null) { | ||||
| list($err, $stdout) = $this->execManualLocal('config %s', $key); | list($err, $stdout) = $this->execManualLocal('config %s', $key); | ||||
| if ($err) { | if ($err) { | ||||
| return $default; | return $default; | ||||
| ▲ Show 20 Lines • Show All 136 Lines • ▼ Show 20 Lines | final class ArcanistGitAPI extends ArcanistRepositoryAPI { | ||||
| public function getAllFiles() { | public function getAllFiles() { | ||||
| $future = $this->buildLocalFuture(array('ls-files -z')); | $future = $this->buildLocalFuture(array('ls-files -z')); | ||||
| return id(new LinesOfALargeExecFuture($future)) | return id(new LinesOfALargeExecFuture($future)) | ||||
| ->setDelimiter("\0"); | ->setDelimiter("\0"); | ||||
| } | } | ||||
| 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); | gitsprintf('%s', $since_commit)); | ||||
| return $this->parseGitRawDiff($stdout); | return $this->parseGitRawDiff($stdout); | ||||
| } | } | ||||
| public function getBlame($path) { | public function getBlame($path) { | ||||
| list($stdout) = $this->execxLocal( | list($stdout) = $this->execxLocal( | ||||
| 'blame --porcelain -w -M %s -- %s', | 'blame --porcelain -w -M %s -- %s', | ||||
| $this->getBaseCommit(), | gitsprintf('%s', $this->getBaseCommit()), | ||||
| $path); | $path); | ||||
| // the --porcelain format prints at least one header line per source line, | // the --porcelain format prints at least one header line per source line, | ||||
| // then the source line prefixed by a tab character | // then the source line prefixed by a tab character | ||||
| $blame_info = preg_split('/^\t.*\n/m', rtrim($stdout)); | $blame_info = preg_split('/^\t.*\n/m', rtrim($stdout)); | ||||
| // commit info is not repeated in these headers, so cache it | // commit info is not repeated in these headers, so cache it | ||||
| $revision_data = array(); | $revision_data = array(); | ||||
| ▲ Show 20 Lines • Show All 70 Lines • ▼ Show 20 Lines | private function getFileDataAtRevision($path, $revision) { | ||||
| if (!strlen($path)) { | if (!strlen($path)) { | ||||
| // No filename, so there's no content (Probably new/deleted file). | // No filename, so there's no content (Probably new/deleted file). | ||||
| return null; | return null; | ||||
| } | } | ||||
| list($stdout) = $this->execxLocal( | list($stdout) = $this->execxLocal( | ||||
| 'ls-tree %s -- %s', | 'ls-tree %s -- %s', | ||||
| $revision, | gitsprintf('%s', $revision), | ||||
| $path); | $path); | ||||
| $info = $this->parseGitTree($stdout); | $info = $this->parseGitTree($stdout); | ||||
| if (empty($info[$path])) { | if (empty($info[$path])) { | ||||
| // No such path, or the path is a directory and we executed 'ls-tree dir/' | // No such path, or the path is a directory and we executed 'ls-tree dir/' | ||||
| // and got a list of its contents back. | // and got a list of its contents back. | ||||
| return null; | return null; | ||||
| } | } | ||||
| if ($info[$path]['type'] != 'blob') { | if ($info[$path]['type'] != 'blob') { | ||||
| // Path is or was a directory, not a file. | // Path is or was a directory, not a file. | ||||
| return null; | return null; | ||||
| } | } | ||||
| list($stdout) = $this->execxLocal( | list($stdout) = $this->execxLocal( | ||||
| 'cat-file blob %s', | 'cat-file blob -- %s', | ||||
| $info[$path]['ref']); | $info[$path]['ref']); | ||||
| return $stdout; | return $stdout; | ||||
| } | } | ||||
| /** | /** | ||||
| * Returns names of all the branches in the current repository. | * Returns names of all the branches in the current repository. | ||||
| * | * | ||||
| * @return list<dict<string, string>> Dictionary of branch information. | * @return list<dict<string, string>> Dictionary of branch information. | ||||
| ▲ Show 20 Lines • Show All 108 Lines • ▼ Show 20 Lines | return pht( | ||||
| 'git push', | 'git push', | ||||
| 'git svn dcommit'); | 'git svn dcommit'); | ||||
| } | } | ||||
| public function getCommitMessage($commit) { | public function getCommitMessage($commit) { | ||||
| list($message) = $this->execxLocal( | list($message) = $this->execxLocal( | ||||
| 'log -n1 --format=%C %s --', | 'log -n1 --format=%C %s --', | ||||
| '%s%n%n%b', | '%s%n%n%b', | ||||
| $commit); | gitsprintf('%s', $commit)); | ||||
| return $message; | return $message; | ||||
| } | } | ||||
| public function loadWorkingCopyDifferentialRevisions( | public function loadWorkingCopyDifferentialRevisions( | ||||
| ConduitClient $conduit, | ConduitClient $conduit, | ||||
| array $query) { | array $query) { | ||||
| $messages = $this->getGitCommitLog(); | $messages = $this->getGitCommitLog(); | ||||
| ▲ Show 20 Lines • Show All 61 Lines • ▼ Show 20 Lines | |||||
| } | } | ||||
| public function getCommitSummary($commit) { | public function getCommitSummary($commit) { | ||||
| if ($commit == self::GIT_MAGIC_ROOT_COMMIT) { | if ($commit == self::GIT_MAGIC_ROOT_COMMIT) { | ||||
| return pht('(The Empty Tree)'); | return pht('(The Empty Tree)'); | ||||
| } | } | ||||
| list($summary) = $this->execxLocal( | list($summary) = $this->execxLocal( | ||||
| 'log -n 1 --format=%C %s', | 'log -n 1 %s %s --', | ||||
| '%s', | '--format=%s', | ||||
| $commit); | gitsprintf('%s', $commit)); | ||||
| return trim($summary); | return trim($summary); | ||||
| } | } | ||||
| public function isGitSubversionRepo() { | public function isGitSubversionRepo() { | ||||
| return Filesystem::pathExists($this->getPath('.git/svn')); | return Filesystem::pathExists($this->getPath('.git/svn')); | ||||
| } | } | ||||
| public function resolveBaseCommitRule($rule, $source) { | public function resolveBaseCommitRule($rule, $source) { | ||||
| list($type, $name) = explode(':', $rule, 2); | list($type, $name) = explode(':', $rule, 2); | ||||
| switch ($type) { | switch ($type) { | ||||
| case 'git': | case 'git': | ||||
| $matches = null; | $matches = null; | ||||
| if (preg_match('/^merge-base\((.+)\)$/', $name, $matches)) { | if (preg_match('/^merge-base\((.+)\)$/', $name, $matches)) { | ||||
| list($err, $merge_base) = $this->execManualLocal( | list($err, $merge_base) = $this->execManualLocal( | ||||
| 'merge-base %s HEAD', | 'merge-base -- %s HEAD', | ||||
| $matches[1]); | $matches[1]); | ||||
| if (!$err) { | if (!$err) { | ||||
| $this->setBaseCommitExplanation( | $this->setBaseCommitExplanation( | ||||
| pht( | pht( | ||||
| "it is the merge-base of '%s' and HEAD, as specified by ". | "it is the merge-base of '%s' and HEAD, as specified by ". | ||||
| "'%s' in your %s 'base' configuration.", | "'%s' in your %s 'base' configuration.", | ||||
| $matches[1], | $matches[1], | ||||
| $rule, | $rule, | ||||
| $source)); | $source)); | ||||
| return trim($merge_base); | return trim($merge_base); | ||||
| } | } | ||||
| } else if (preg_match('/^branch-unique\((.+)\)$/', $name, $matches)) { | } else if (preg_match('/^branch-unique\((.+)\)$/', $name, $matches)) { | ||||
| list($err, $merge_base) = $this->execManualLocal( | list($err, $merge_base) = $this->execManualLocal( | ||||
| 'merge-base %s HEAD', | 'merge-base -- %s HEAD', | ||||
| $matches[1]); | $matches[1]); | ||||
| if ($err) { | if ($err) { | ||||
| return null; | return null; | ||||
| } | } | ||||
| $merge_base = trim($merge_base); | $merge_base = trim($merge_base); | ||||
| list($commits) = $this->execxLocal( | list($commits) = $this->execxLocal( | ||||
| 'log --format=%C %s..HEAD --', | 'log --format=%C %s..HEAD --', | ||||
| ▲ Show 20 Lines • Show All 101 Lines • ▼ Show 20 Lines | switch ($type) { | ||||
| break; | break; | ||||
| case 'upstream': | case 'upstream': | ||||
| list($err, $upstream) = $this->execManualLocal( | list($err, $upstream) = $this->execManualLocal( | ||||
| 'rev-parse --abbrev-ref --symbolic-full-name %s', | 'rev-parse --abbrev-ref --symbolic-full-name %s', | ||||
| '@{upstream}'); | '@{upstream}'); | ||||
| if (!$err) { | if (!$err) { | ||||
| $upstream = rtrim($upstream); | $upstream = rtrim($upstream); | ||||
| list($upstream_merge_base) = $this->execxLocal( | list($upstream_merge_base) = $this->execxLocal( | ||||
| 'merge-base %s HEAD', | 'merge-base -- %s HEAD', | ||||
| $upstream); | $upstream); | ||||
| $upstream_merge_base = rtrim($upstream_merge_base); | $upstream_merge_base = rtrim($upstream_merge_base); | ||||
| $this->setBaseCommitExplanation( | $this->setBaseCommitExplanation( | ||||
| pht( | pht( | ||||
| "it is the merge-base of the upstream of the current branch ". | "it is the merge-base of the upstream of the current branch ". | ||||
| "and HEAD, and matched the rule '%s' in your %s ". | "and HEAD, and matched the rule '%s' in your %s ". | ||||
| "'base' configuration.", | "'base' configuration.", | ||||
| $rule, | $rule, | ||||
| ▲ Show 20 Lines • Show All 412 Lines • Show Last 20 Lines | |||||