Changeset View
Changeset View
Standalone View
Standalone View
src/repository/api/ArcanistGitAPI.php
| Show First 20 Lines • Show All 86 Lines • ▼ Show 20 Lines | private function isDescendant($child, $parent) { | ||||
| return ($common_ancestor == $parent) && ($common_ancestor != $child); | return ($common_ancestor == $parent) && ($common_ancestor != $child); | ||||
| } | } | ||||
| public function getLocalCommitInformation() { | public function getLocalCommitInformation() { | ||||
| if ($this->repositoryHasNoCommits) { | if ($this->repositoryHasNoCommits) { | ||||
| // Zero commits. | // Zero commits. | ||||
| throw new Exception( | throw new Exception( | ||||
| pht( | |||||
| "You can't get local commit information for a repository with no ". | "You can't get local commit information for a repository with no ". | ||||
| "commits."); | "commits.")); | ||||
| } else if ($this->getBaseCommit() == self::GIT_MAGIC_ROOT_COMMIT) { | } else if ($this->getBaseCommit() == self::GIT_MAGIC_ROOT_COMMIT) { | ||||
| // One commit. | // One commit. | ||||
| $against = 'HEAD'; | $against = 'HEAD'; | ||||
| } else { | } else { | ||||
| // 2..N commits. We include commits reachable from HEAD which are | // 2..N commits. We include commits reachable from HEAD which are | ||||
| // not reachable from the base commit; this is consistent with user | // not reachable from the base commit; this is consistent with user | ||||
| // expectations even though it is not actually the diff range. | // expectations even though it is not actually the diff range. | ||||
| ▲ Show 20 Lines • Show All 90 Lines • ▼ Show 20 Lines | public function getLocalCommitInformation() { | ||||
| return $commits; | return $commits; | ||||
| } | } | ||||
| protected function buildBaseCommit($symbolic_commit) { | 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( | ||||
| '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( | ||||
| "Unable to find any git commit named '{$symbolic_commit}' in ". | pht( | ||||
| "this repository."); | "Unable to find any git commit named '%s' in this repository.", | ||||
| $symbolic_commit)); | |||||
| } | } | ||||
| if ($this->symbolicHeadCommit === null) { | if ($this->symbolicHeadCommit === null) { | ||||
| $this->setBaseCommitExplanation( | $this->setBaseCommitExplanation( | ||||
| pht( | |||||
| "it is the merge-base of the explicitly specified base commit ". | "it is the merge-base of the explicitly specified base commit ". | ||||
| "'{$symbolic_commit}' and HEAD."); | "'%s' and HEAD.", | ||||
| $symbolic_commit)); | |||||
| } else { | } else { | ||||
| $this->setBaseCommitExplanation( | $this->setBaseCommitExplanation( | ||||
| pht( | |||||
| "it is the merge-base of the explicitly specified base commit ". | "it is the merge-base of the explicitly specified base commit ". | ||||
| "'{$symbolic_commit}' and the explicitly specified head ". | "'%s' and the explicitly specified head commit '%s'.", | ||||
| "commit '{$this->symbolicHeadCommit}'."); | $symbolic_commit, | ||||
| $this->symbolicHeadCommit)); | |||||
| } | } | ||||
| return trim($merge_base); | return trim($merge_base); | ||||
| } | } | ||||
| // Detect zero-commit or one-commit repositories. There is only one | // Detect zero-commit or one-commit repositories. There is only one | ||||
| // relative-commit value that makes any sense in these repositories: the | // relative-commit value that makes any sense in these repositories: the | ||||
| // empty tree. | // empty tree. | ||||
| list($err) = $this->execManualLocal('rev-parse --verify HEAD^'); | list($err) = $this->execManualLocal('rev-parse --verify HEAD^'); | ||||
| if ($err) { | if ($err) { | ||||
| list($err) = $this->execManualLocal('rev-parse --verify HEAD'); | list($err) = $this->execManualLocal('rev-parse --verify HEAD'); | ||||
| if ($err) { | if ($err) { | ||||
| $this->repositoryHasNoCommits = true; | $this->repositoryHasNoCommits = true; | ||||
| } | } | ||||
| if ($this->repositoryHasNoCommits) { | if ($this->repositoryHasNoCommits) { | ||||
| $this->setBaseCommitExplanation( | $this->setBaseCommitExplanation(pht('the repository has no commits.')); | ||||
| 'the repository has no commits.'); | |||||
| } else { | } else { | ||||
| $this->setBaseCommitExplanation( | $this->setBaseCommitExplanation( | ||||
| 'the repository has only one commit.'); | pht('the repository has only one commit.')); | ||||
| } | } | ||||
| return self::GIT_MAGIC_ROOT_COMMIT; | return self::GIT_MAGIC_ROOT_COMMIT; | ||||
| } | } | ||||
| if ($this->getBaseCommitArgumentRules() || | if ($this->getBaseCommitArgumentRules() || | ||||
| $this->getConfigurationManager()->getConfigFromAnySource('base')) { | $this->getConfigurationManager()->getConfigFromAnySource('base')) { | ||||
| $base = $this->resolveBaseCommit(); | $base = $this->resolveBaseCommit(); | ||||
| if (!$base) { | if (!$base) { | ||||
| throw new ArcanistUsageException( | throw new ArcanistUsageException( | ||||
| pht( | |||||
| "None of the rules in your 'base' configuration matched a valid ". | "None of the rules in your 'base' configuration matched a valid ". | ||||
| "commit. Adjust rules or specify which commit you want to use ". | "commit. Adjust rules or specify which commit you want to use ". | ||||
| "explicitly."); | "explicitly.")); | ||||
| } | } | ||||
| return $base; | return $base; | ||||
| } | } | ||||
| $do_write = false; | $do_write = false; | ||||
| $default_relative = null; | $default_relative = null; | ||||
| $working_copy = $this->getWorkingCopyIdentity(); | $working_copy = $this->getWorkingCopyIdentity(); | ||||
| if ($working_copy) { | if ($working_copy) { | ||||
| $default_relative = $working_copy->getProjectConfig( | $default_relative = $working_copy->getProjectConfig( | ||||
| 'git.default-relative-commit'); | 'git.default-relative-commit'); | ||||
| $this->setBaseCommitExplanation( | $this->setBaseCommitExplanation( | ||||
| "it is the merge-base of '{$default_relative}' and HEAD, as ". | pht( | ||||
| "specified in 'git.default-relative-commit' in '.arcconfig'. This ". | "it is the merge-base of '%s' and HEAD, as specified in '%s' in ". | ||||
| "setting overrides other settings."); | "'%s'. This setting overrides other settings.", | ||||
| $default_relative, | |||||
| 'git.default-relative-commit', | |||||
| '.arcconfig')); | |||||
| } | } | ||||
| if (!$default_relative) { | if (!$default_relative) { | ||||
| 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) { | ||||
| $default_relative = trim($upstream); | $default_relative = trim($upstream); | ||||
| $this->setBaseCommitExplanation( | $this->setBaseCommitExplanation( | ||||
| "it is the merge-base of '{$default_relative}' (the Git upstream ". | pht( | ||||
| "of the current branch) HEAD."); | "it is the merge-base of '%s' (the Git upstream ". | ||||
| "of the current branch) HEAD.", | |||||
| $default_relative)); | |||||
| } | } | ||||
| } | } | ||||
| if (!$default_relative) { | if (!$default_relative) { | ||||
| $default_relative = $this->readScratchFile('default-relative-commit'); | $default_relative = $this->readScratchFile('default-relative-commit'); | ||||
| $default_relative = trim($default_relative); | $default_relative = trim($default_relative); | ||||
| if ($default_relative) { | if ($default_relative) { | ||||
| $this->setBaseCommitExplanation( | $this->setBaseCommitExplanation( | ||||
| "it is the merge-base of '{$default_relative}' and HEAD, as ". | pht( | ||||
| "specified in '.git/arc/default-relative-commit'."); | "it is the merge-base of '%s' and HEAD, as specified in '%s'.", | ||||
| $default_relative, | |||||
| '.git/arc/default-relative-commit')); | |||||
| } | } | ||||
| } | } | ||||
| if (!$default_relative) { | if (!$default_relative) { | ||||
| // TODO: Remove the history lesson soon. | // TODO: Remove the history lesson soon. | ||||
| echo phutil_console_format( | echo phutil_console_format( | ||||
| "<bg:green>** Select a Default Commit Range **</bg>\n\n"); | "<bg:green>** Select a Default Commit Range **</bg>\n\n"); | ||||
| echo phutil_console_wrap( | echo phutil_console_wrap( | ||||
| pht( | |||||
| "You're running a command which operates on a range of revisions ". | "You're running a command which operates on a range of revisions ". | ||||
| "(usually, from some revision to HEAD) but have not specified the ". | "(usually, from some revision to HEAD) but have not specified the ". | ||||
| "revision that should determine the start of the range.\n\n". | "revision that should determine the start of the range.\n\n". | ||||
| "Previously, arc assumed you meant 'HEAD^' when you did not specify ". | "Previously, arc assumed you meant '%s' when you did not specify ". | ||||
| "a start revision, but this behavior does not make much sense in ". | "a start revision, but this behavior does not make much sense in ". | ||||
| "most workflows outside of Facebook's historic git-svn workflow.\n\n". | "most workflows outside of Facebook's historic %s workflow.\n\n". | ||||
| "arc no longer assumes 'HEAD^'. You must specify a relative commit ". | "arc no longer assumes '%s'. You must specify a relative commit ". | ||||
| "explicitly when you invoke a command (e.g., `arc diff HEAD^`, not ". | "explicitly when you invoke a command (e.g., `%s`, not just `%s`) ". | ||||
| "just `arc diff`) or select a default for this working copy.\n\n". | "or select a default for this working copy.\n\nIn most cases, the ". | ||||
| "In most cases, the best default is 'origin/master'. You can also ". | "best default is '%s'. You can also select '%s' to preserve the ". | ||||
| "select 'HEAD^' to preserve the old behavior, or some other remote ". | "old behavior, or some other remote or branch. But you almost ". | ||||
| "or branch. But you almost certainly want to select ". | "certainly want to select 'origin/master'.\n\n". | ||||
| "'origin/master'.\n\n". | |||||
| "(Technically: the merge-base of the selected revision and HEAD is ". | "(Technically: the merge-base of the selected revision and HEAD is ". | ||||
| "used to determine the start of the commit range.)"); | "used to determine the start of the commit range.)", | ||||
| 'HEAD^', | |||||
| 'git-svn', | |||||
| 'HEAD^', | |||||
| 'arc diff HEAD^', | |||||
| 'arc diff', | |||||
| 'origin/master', | |||||
| 'HEAD^')); | |||||
| $prompt = 'What default do you want to use? [origin/master]'; | $prompt = pht('What default do you want to use? [origin/master]'); | ||||
| $default = phutil_console_prompt($prompt); | $default = phutil_console_prompt($prompt); | ||||
| if (!strlen(trim($default))) { | if (!strlen(trim($default))) { | ||||
| $default = 'origin/master'; | $default = 'origin/master'; | ||||
| } | } | ||||
| $default_relative = $default; | $default_relative = $default; | ||||
| $do_write = true; | $do_write = true; | ||||
| } | } | ||||
| list($object_type) = $this->execxLocal( | list($object_type) = $this->execxLocal( | ||||
| 'cat-file -t %s', | 'cat-file -t %s', | ||||
| $default_relative); | $default_relative); | ||||
| if (trim($object_type) !== 'commit') { | if (trim($object_type) !== 'commit') { | ||||
| throw new Exception( | throw new Exception( | ||||
| "Relative commit '{$default_relative}' is not the name of a commit!"); | pht( | ||||
| "Relative commit '%s' is not the name of a commit!", | |||||
| $default_relative)); | |||||
| } | } | ||||
| if ($do_write) { | if ($do_write) { | ||||
| // Don't perform this write until we've verified that the object is a | // Don't perform this write until we've verified that the object is a | ||||
| // valid commit name. | // valid commit name. | ||||
| $this->writeScratchFile('default-relative-commit', $default_relative); | $this->writeScratchFile('default-relative-commit', $default_relative); | ||||
| $this->setBaseCommitExplanation( | $this->setBaseCommitExplanation( | ||||
| "it is the merge-base of '{$default_relative}' and HEAD, as you ". | pht( | ||||
| "just specified."); | "it is the merge-base of '%s' and HEAD, as you just specified.", | ||||
| $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); | ||||
| } | } | ||||
| Show All 20 Lines | final class ArcanistGitAPI extends ArcanistRepositoryAPI { | ||||
| */ | */ | ||||
| private function resolveCommit($symbolic_commit) { | private function resolveCommit($symbolic_commit) { | ||||
| list($err, $commit_hash) = $this->execManualLocal( | list($err, $commit_hash) = $this->execManualLocal( | ||||
| 'rev-parse %s', | 'rev-parse %s', | ||||
| $symbolic_commit); | $symbolic_commit); | ||||
| if ($err) { | if ($err) { | ||||
| throw new ArcanistUsageException( | throw new ArcanistUsageException( | ||||
| "Unable to find any git commit named '{$symbolic_commit}' in ". | pht( | ||||
| "this repository."); | "Unable to find any git commit named '%s' in this repository.", | ||||
| $symbolic_commit)); | |||||
| } | } | ||||
| return trim($commit_hash); | return trim($commit_hash); | ||||
| } | } | ||||
| private function getDiffFullOptions($detect_moves_and_renames = true) { | private function getDiffFullOptions($detect_moves_and_renames = true) { | ||||
| $options = array( | $options = array( | ||||
| self::getDiffBaseOptions(), | self::getDiffBaseOptions(), | ||||
| ▲ Show 20 Lines • Show All 147 Lines • ▼ Show 20 Lines | final class ArcanistGitAPI extends ArcanistRepositoryAPI { | ||||
| private function executeSVNFindRev($input, $vcs) { | private function executeSVNFindRev($input, $vcs) { | ||||
| $match = array(); | $match = array(); | ||||
| list($stdout) = $this->execxLocal( | list($stdout) = $this->execxLocal( | ||||
| 'svn find-rev %s', | 'svn find-rev %s', | ||||
| $input); | $input); | ||||
| if (!$stdout) { | if (!$stdout) { | ||||
| throw new ArcanistUsageException( | throw new ArcanistUsageException( | ||||
| "Cannot find the {$vcs} equivalent of {$input}."); | pht( | ||||
| 'Cannot find the %s equivalent of %s.', | |||||
| $vcs, | |||||
| $input)); | |||||
| } | } | ||||
| // When git performs a partial-rebuild during svn | // When git performs a partial-rebuild during svn | ||||
| // look-up, we need to parse the final line | // look-up, we need to parse the final line | ||||
| $lines = explode("\n", $stdout); | $lines = explode("\n", $stdout); | ||||
| $stdout = $lines[count($lines) - 2]; | $stdout = $lines[count($lines) - 2]; | ||||
| return rtrim($stdout); | return rtrim($stdout); | ||||
| } | } | ||||
| ▲ Show 20 Lines • Show All 213 Lines • ▼ Show 20 Lines | foreach (explode("\n", trim($stdout)) as $line) { | ||||
| } | } | ||||
| $matches = null; | $matches = null; | ||||
| $ok = preg_match( | $ok = preg_match( | ||||
| '/^([0-9a-f]+)[^(]+?[(](.*?) +\d\d\d\d-\d\d-\d\d/', | '/^([0-9a-f]+)[^(]+?[(](.*?) +\d\d\d\d-\d\d-\d\d/', | ||||
| $line, | $line, | ||||
| $matches); | $matches); | ||||
| if (!$ok) { | if (!$ok) { | ||||
| throw new Exception("Bad blame? `{$line}'"); | throw new Exception(pht("Bad blame? `%s'", $line)); | ||||
| } | } | ||||
| $revision = $matches[1]; | $revision = $matches[1]; | ||||
| $author = $matches[2]; | $author = $matches[2]; | ||||
| $blame[] = array($author, $revision); | $blame[] = array($author, $revision); | ||||
| } | } | ||||
| return $blame; | return $blame; | ||||
| Show All 18 Lines | private function parseGitTree($stdout) { | ||||
| $lines = explode("\n", $stdout); | $lines = explode("\n", $stdout); | ||||
| foreach ($lines as $line) { | foreach ($lines as $line) { | ||||
| $matches = array(); | $matches = array(); | ||||
| $ok = preg_match( | $ok = preg_match( | ||||
| '/^(\d{6}) (blob|tree|commit) ([a-z0-9]{40})[\t](.*)$/', | '/^(\d{6}) (blob|tree|commit) ([a-z0-9]{40})[\t](.*)$/', | ||||
| $line, | $line, | ||||
| $matches); | $matches); | ||||
| if (!$ok) { | if (!$ok) { | ||||
| throw new Exception('Failed to parse git ls-tree output!'); | throw new Exception(pht('Failed to parse %s output!', 'git ls-tree')); | ||||
| } | } | ||||
| $result[$matches[4]] = array( | $result[$matches[4]] = array( | ||||
| 'mode' => $matches[1], | 'mode' => $matches[1], | ||||
| 'type' => $matches[2], | 'type' => $matches[2], | ||||
| 'ref' => $matches[3], | 'ref' => $matches[3], | ||||
| ); | ); | ||||
| } | } | ||||
| return $result; | return $result; | ||||
| ▲ Show 20 Lines • Show All 108 Lines • ▼ Show 20 Lines | final class ArcanistGitAPI extends ArcanistRepositoryAPI { | ||||
| public function supportsLocalBranchMerge() { | public function supportsLocalBranchMerge() { | ||||
| return true; | return true; | ||||
| } | } | ||||
| public function performLocalBranchMerge($branch, $message) { | public function performLocalBranchMerge($branch, $message) { | ||||
| if (!$branch) { | if (!$branch) { | ||||
| throw new ArcanistUsageException( | throw new ArcanistUsageException( | ||||
| 'Under git, you must specify the branch you want to merge.'); | pht('Under git, you must specify the branch you want to merge.')); | ||||
| } | } | ||||
| $err = phutil_passthru( | $err = phutil_passthru( | ||||
| '(cd %s && git merge --no-ff -m %s %s)', | '(cd %s && git merge --no-ff -m %s %s)', | ||||
| $this->getPath(), | $this->getPath(), | ||||
| $message, | $message, | ||||
| $branch); | $branch); | ||||
| if ($err) { | if ($err) { | ||||
| throw new ArcanistUsageException('Merge failed!'); | throw new ArcanistUsageException('Merge failed!'); | ||||
| } | } | ||||
| } | } | ||||
| public function getFinalizedRevisionMessage() { | public function getFinalizedRevisionMessage() { | ||||
| return "You may now push this commit upstream, as appropriate (e.g. with ". | return pht( | ||||
| "'git push', or 'git svn dcommit', or by printing and faxing it)."; | "You may now push this commit upstream, as appropriate (e.g. with ". | ||||
| "'%s', or '%s', or by printing and faxing it).", | |||||
| 'git push', | |||||
| '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); | $commit); | ||||
| return $message; | return $message; | ||||
| Show All 27 Lines | if ($revision_ids) { | ||||
| $results = $conduit->callMethodSynchronous( | $results = $conduit->callMethodSynchronous( | ||||
| 'differential.query', | 'differential.query', | ||||
| $query + array( | $query + array( | ||||
| 'ids' => $revision_ids, | 'ids' => $revision_ids, | ||||
| )); | )); | ||||
| foreach ($results as $key => $result) { | foreach ($results as $key => $result) { | ||||
| $hash = substr($reason_map[$result['id']], 0, 16); | $hash = substr($reason_map[$result['id']], 0, 16); | ||||
| $results[$key]['why'] = | $results[$key]['why'] = pht( | ||||
| "Commit message for '{$hash}' has explicit 'Differential Revision'."; | "Commit message for '%s' has explicit 'Differential Revision'.", | ||||
| $hash); | |||||
| } | } | ||||
| return $results; | return $results; | ||||
| } | } | ||||
| // If we didn't succeed, try to find revisions by hash. | // If we didn't succeed, try to find revisions by hash. | ||||
| $hashes = array(); | $hashes = array(); | ||||
| foreach ($this->getLocalCommitInformation() as $commit) { | foreach ($this->getLocalCommitInformation() as $commit) { | ||||
| $hashes[] = array('gtcm', $commit['commit']); | $hashes[] = array('gtcm', $commit['commit']); | ||||
| $hashes[] = array('gttr', $commit['tree']); | $hashes[] = array('gttr', $commit['tree']); | ||||
| } | } | ||||
| $results = $conduit->callMethodSynchronous( | $results = $conduit->callMethodSynchronous( | ||||
| 'differential.query', | 'differential.query', | ||||
| $query + array( | $query + array( | ||||
| 'commitHashes' => $hashes, | 'commitHashes' => $hashes, | ||||
| )); | )); | ||||
| foreach ($results as $key => $result) { | foreach ($results as $key => $result) { | ||||
| $results[$key]['why'] = | $results[$key]['why'] = pht( | ||||
| 'A git commit or tree hash in the commit range is already attached '. | 'A git commit or tree hash in the commit range is already attached '. | ||||
| 'to the Differential revision.'; | 'to the Differential revision.'); | ||||
| } | } | ||||
| return $results; | return $results; | ||||
| } | } | ||||
| public function updateWorkingCopy() { | public function updateWorkingCopy() { | ||||
| $this->execxLocal('pull'); | $this->execxLocal('pull'); | ||||
| $this->reloadWorkingCopy(); | $this->reloadWorkingCopy(); | ||||
| } | } | ||||
| public function getCommitSummary($commit) { | public function getCommitSummary($commit) { | ||||
| if ($commit == self::GIT_MAGIC_ROOT_COMMIT) { | if ($commit == self::GIT_MAGIC_ROOT_COMMIT) { | ||||
| return '(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 --format=%C %s', | ||||
| '%s', | '%s', | ||||
| $commit); | $commit); | ||||
| return trim($summary); | return trim($summary); | ||||
| } | } | ||||
| public function backoutCommit($commit_hash) { | public function backoutCommit($commit_hash) { | ||||
| $this->execxLocal( | $this->execxLocal('revert %s -n --no-edit', $commit_hash); | ||||
| 'revert %s -n --no-edit', $commit_hash); | |||||
| $this->reloadWorkingCopy(); | $this->reloadWorkingCopy(); | ||||
| if (!$this->getUncommittedStatus()) { | if (!$this->getUncommittedStatus()) { | ||||
| throw new ArcanistUsageException( | throw new ArcanistUsageException( | ||||
| "{$commit_hash} has already been reverted."); | pht('%s has already been reverted.', $commit_hash)); | ||||
| } | } | ||||
| } | } | ||||
| public function getBackoutMessage($commit_hash) { | public function getBackoutMessage($commit_hash) { | ||||
| return 'This reverts commit '.$commit_hash.'.'; | return pht('This reverts commit %s.', $commit_hash); | ||||
| } | } | ||||
| 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( | ||||
| "it is the merge-base of '{$matches[1]}' and HEAD, as ". | pht( | ||||
| "specified by '{$rule}' in your {$source} 'base' ". | "it is the merge-base of '%s' and HEAD, as specified by ". | ||||
| "configuration."); | "'%s' in your %s 'base' configuration.", | ||||
| $matches[1], | |||||
| $rule, | |||||
| $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; | ||||
| Show All 26 Lines | switch ($type) { | ||||
| // for whatever reason. | // for whatever reason. | ||||
| $head_branch_count = count($branches); | $head_branch_count = count($branches); | ||||
| } else if (count($branches) > $head_branch_count) { | } else if (count($branches) > $head_branch_count) { | ||||
| foreach ($branches as $key => $branch) { | foreach ($branches as $key => $branch) { | ||||
| $branches[$key] = trim($branch, ' *'); | $branches[$key] = trim($branch, ' *'); | ||||
| } | } | ||||
| $branches = implode(', ', $branches); | $branches = implode(', ', $branches); | ||||
| $this->setBaseCommitExplanation( | $this->setBaseCommitExplanation( | ||||
| "it is the first commit between '{$merge_base}' (the ". | pht( | ||||
| "merge-base of '{$matches[1]}' and HEAD) which is also ". | "it is the first commit between '%s' (the merge-base of ". | ||||
| "contained by another branch ({$branches})."); | "'%s' and HEAD) which is also contained by another branch ". | ||||
| "(%s).", | |||||
| $merge_base, | |||||
| $matches[1], | |||||
| $branches)); | |||||
| return $commit; | return $commit; | ||||
| } | } | ||||
| } | } | ||||
| } else { | } else { | ||||
| list($err) = $this->execManualLocal( | list($err) = $this->execManualLocal( | ||||
| 'cat-file -t %s', | 'cat-file -t %s', | ||||
| $name); | $name); | ||||
| if (!$err) { | if (!$err) { | ||||
| $this->setBaseCommitExplanation( | $this->setBaseCommitExplanation( | ||||
| "it is specified by '{$rule}' in your {$source} 'base' ". | pht( | ||||
| "configuration."); | "it is specified by '%s' in your %s 'base' configuration.", | ||||
| $rule, | |||||
| $source)); | |||||
| return $name; | return $name; | ||||
| } | } | ||||
| } | } | ||||
| break; | break; | ||||
| case 'arc': | case 'arc': | ||||
| switch ($name) { | switch ($name) { | ||||
| case 'empty': | case 'empty': | ||||
| $this->setBaseCommitExplanation( | $this->setBaseCommitExplanation( | ||||
| "you specified '{$rule}' in your {$source} 'base' ". | pht( | ||||
| "configuration."); | "you specified '%s' in your %s 'base' configuration.", | ||||
| $rule, | |||||
| $source)); | |||||
| return self::GIT_MAGIC_ROOT_COMMIT; | return self::GIT_MAGIC_ROOT_COMMIT; | ||||
| case 'amended': | case 'amended': | ||||
| $text = $this->getCommitMessage('HEAD'); | $text = $this->getCommitMessage('HEAD'); | ||||
| $message = ArcanistDifferentialCommitMessage::newFromRawCorpus( | $message = ArcanistDifferentialCommitMessage::newFromRawCorpus( | ||||
| $text); | $text); | ||||
| if ($message->getRevisionID()) { | if ($message->getRevisionID()) { | ||||
| $this->setBaseCommitExplanation( | $this->setBaseCommitExplanation( | ||||
| pht( | |||||
| "HEAD has been amended with 'Differential Revision:', ". | "HEAD has been amended with 'Differential Revision:', ". | ||||
| "as specified by '{$rule}' in your {$source} 'base' ". | "as specified by '%s' in your %s 'base' configuration.", | ||||
| "configuration."); | $rule, | ||||
| $source)); | |||||
| return 'HEAD^'; | return 'HEAD^'; | ||||
| } | } | ||||
| 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( | |||||
| "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 '{$rule}' in your {$source} ". | "and HEAD, and matched the rule '%s' in your %s ". | ||||
| "'base' configuration."); | "'base' configuration.", | ||||
| $rule, | |||||
| $source)); | |||||
| return $upstream_merge_base; | return $upstream_merge_base; | ||||
| } | } | ||||
| break; | break; | ||||
| case 'this': | case 'this': | ||||
| $this->setBaseCommitExplanation( | $this->setBaseCommitExplanation( | ||||
| "you specified '{$rule}' in your {$source} 'base' ". | pht( | ||||
| "configuration."); | "you specified '%s' in your %s 'base' configuration.", | ||||
| $rule, | |||||
| $source)); | |||||
| return 'HEAD^'; | return 'HEAD^'; | ||||
| } | } | ||||
| default: | default: | ||||
| return null; | return null; | ||||
| } | } | ||||
| return null; | return null; | ||||
| } | } | ||||
| Show All 20 Lines | |||||