Changeset View
Changeset View
Standalone View
Standalone View
src/repository/api/ArcanistGitAPI.php
| Show First 20 Lines • Show All 476 Lines • ▼ Show 20 Lines | public function getRawDiffText($path, $detect_moves_and_renames = true) { | ||||
| $options = $this->getDiffFullOptions($detect_moves_and_renames); | $options = $this->getDiffFullOptions($detect_moves_and_renames); | ||||
| list($stdout) = $this->execxLocal( | list($stdout) = $this->execxLocal( | ||||
| "diff {$options} %s -- %s", | "diff {$options} %s -- %s", | ||||
| $this->getBaseCommit(), | $this->getBaseCommit(), | ||||
| $path); | $path); | ||||
| return $stdout; | return $stdout; | ||||
| } | } | ||||
| public function getBranchName() { | private function getBranchNameFromRef($ref) { | ||||
| // TODO: consider: | $count = 0; | ||||
| // | $branch = preg_replace('/^refs\/heads\//', '', $ref, 1, $count); | ||||
| // $ git rev-parse --abbrev-ref `git symbolic-ref HEAD` | if ($count !== 1) { | ||||
| // | return null; | ||||
epriestley: It looks like this fails if there is a branch named `HEAD`, but plain `git status` also emits… | |||||
Done Inline ActionsA branch named HEAD doesn't really seem supported by git itself, no (and it's quite far along the path of evil and perversion anyway): beta|master:~/code/arcanist$ git branch HEAD fatal: it does not make sense to create 'HEAD' manually You're right, this logic fails with a tag and branch of the same name and I can't find appropriate flags to make rev-parse ignore tags. I'll update this diff with a symbolic-ref approach, which does seem cleaner. jbeta: A branch named `HEAD` doesn't really seem supported by git itself, no (and it's quite far along… | |||||
| // But that may fail if you're not on a branch. | } | ||||
| list($stdout) = $this->execxLocal('branch --no-color'); | |||||
| // Assume that any branch beginning with '(' means 'no branch', or whatever | return $branch; | ||||
| // 'no branch' is in the current locale. | |||||
| $matches = null; | |||||
| if (preg_match('/^\* ([^\(].*)$/m', $stdout, $matches)) { | |||||
| return $matches[1]; | |||||
| } | } | ||||
| public function getBranchName() { | |||||
| list($err, $stdout, $stderr) = $this->execManualLocal( | |||||
| 'symbolic-ref --quiet HEAD'); | |||||
| if ($err === 0) { | |||||
| // We expect the branch name to come qualified with a refs/heads/ prefix. | |||||
| // Verify this, and strip it. | |||||
| $ref = rtrim($stdout); | |||||
| $branch = $this->getBranchNameFromRef($ref); | |||||
| if (!$branch) { | |||||
| throw new Exception( | |||||
| pht('Failed to parse %s output!', 'git symbolic-ref')); | |||||
| } | |||||
| return $branch; | |||||
| } else if ($err === 1) { | |||||
| // Exit status 1 with --quiet indicates that HEAD is detached. | |||||
| return null; | return null; | ||||
| } else { | |||||
| throw new Exception( | |||||
| pht('Command %s failed: %s', 'git symbolic-ref', $stderr)); | |||||
| } | |||||
| } | } | ||||
| public function getRemoteURI() { | public function getRemoteURI() { | ||||
| list($stdout) = $this->execxLocal('ls-remote --get-url origin'); | list($stdout) = $this->execxLocal('ls-remote --get-url origin'); | ||||
| $uri = rtrim($stdout); | $uri = rtrim($stdout); | ||||
| if ($uri === 'origin') { | if ($uri === 'origin') { | ||||
| return null; | return null; | ||||
| ▲ Show 20 Lines • Show All 372 Lines • ▼ Show 20 Lines | final class ArcanistGitAPI extends ArcanistRepositoryAPI { | ||||
| } | } | ||||
| /** | /** | ||||
| * 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. | ||||
| */ | */ | ||||
| public function getAllBranches() { | public function getAllBranches() { | ||||
| list($branch_info) = $this->execxLocal( | list($ref_list) = $this->execxLocal( | ||||
| 'branch --no-color'); | 'for-each-ref --format=%s refs/heads', | ||||
Done Inline ActionsThe "branch and tag with the same name" case also breaks this: these shortnames include unneeded heads/ prefixes. Will update. jbeta: The "branch and tag with the same name" case also breaks this: these shortnames include… | |||||
| $lines = explode("\n", rtrim($branch_info)); | '%(refname)'); | ||||
| $refs = explode("\n", rtrim($ref_list)); | |||||
| $current = $this->getBranchName(); | |||||
| $result = array(); | $result = array(); | ||||
| foreach ($lines as $line) { | foreach ($refs as $ref) { | ||||
| $branch = $this->getBranchNameFromRef($ref); | |||||
| if (preg_match('@^[* ]+\(no branch|detached from \w+/\w+\)@', $line)) { | if ($branch) { | ||||
| // This is indicating that the working copy is in a detached state; | |||||
| // just ignore it. | |||||
| continue; | |||||
| } | |||||
| list($current, $name) = preg_split('/\s+/', $line, 2); | |||||
| $result[] = array( | $result[] = array( | ||||
| 'current' => !empty($current), | 'current' => ($branch === $current), | ||||
| 'name' => $name, | 'name' => $branch, | ||||
| ); | ); | ||||
| } | } | ||||
| } | |||||
| return $result; | return $result; | ||||
| } | } | ||||
| public function getWorkingCopyRevision() { | public function getWorkingCopyRevision() { | ||||
| list($stdout) = $this->execxLocal('rev-parse HEAD'); | list($stdout) = $this->execxLocal('rev-parse HEAD'); | ||||
| return rtrim($stdout, "\n"); | return rtrim($stdout, "\n"); | ||||
| } | } | ||||
| ▲ Show 20 Lines • Show All 213 Lines • ▼ Show 20 Lines | switch ($type) { | ||||
| if (!$commits) { | if (!$commits) { | ||||
| return null; | return null; | ||||
| } | } | ||||
| $commits[] = $merge_base; | $commits[] = $merge_base; | ||||
| $head_branch_count = null; | $head_branch_count = null; | ||||
| $all_branch_names = ipull($this->getAllBranches(), 'name'); | |||||
| foreach ($commits as $commit) { | foreach ($commits as $commit) { | ||||
| // Ideally, we would use something like "for-each-ref --contains" | |||||
| // to get a filtered list of branches ready for script consumption. | |||||
| // Instead, try to get predictable output from "branch --contains". | |||||
| list($branches) = $this->execxLocal( | list($branches) = $this->execxLocal( | ||||
| 'branch --contains %s', | '-c column.ui=never -c color.ui=never branch --contains %s', | ||||
| $commit); | $commit); | ||||
| $branches = array_filter(explode("\n", $branches)); | $branches = array_filter(explode("\n", $branches)); | ||||
| // Filter the list, removing the "current" marker (*) and ignoring | |||||
| // anything other than known branch names (mainly, any possible | |||||
| // "detached HEAD" or "no branch" line). | |||||
| foreach ($branches as $key => $branch) { | |||||
| $branch = trim($branch, ' *'); | |||||
| if (in_array($branch, $all_branch_names)) { | |||||
| $branches[$key] = $branch; | |||||
| } else { | |||||
| unset($branches[$key]); | |||||
| } | |||||
| } | |||||
| if ($head_branch_count === null) { | if ($head_branch_count === null) { | ||||
| // If this is the first commit, it's HEAD. Count how many | // If this is the first commit, it's HEAD. Count how many | ||||
| // branches it is on; we want to include commits on the same | // branches it is on; we want to include commits on the same | ||||
| // number of branches. This covers a case where this branch | // number of branches. This covers a case where this branch | ||||
| // has sub-branches and we're running "arc diff" here again | // has sub-branches and we're running "arc diff" here again | ||||
| // 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) { | |||||
| $branches[$key] = trim($branch, ' *'); | |||||
| } | |||||
| $branches = implode(', ', $branches); | $branches = implode(', ', $branches); | ||||
| $this->setBaseCommitExplanation( | $this->setBaseCommitExplanation( | ||||
| pht( | pht( | ||||
| "it is the first commit between '%s' (the merge-base of ". | "it is the first commit between '%s' (the merge-base of ". | ||||
| "'%s' and HEAD) which is also contained by another branch ". | "'%s' and HEAD) which is also contained by another branch ". | ||||
| "(%s).", | "(%s).", | ||||
| $merge_base, | $merge_base, | ||||
| $matches[1], | $matches[1], | ||||
| ▲ Show 20 Lines • Show All 95 Lines • Show Last 20 Lines | |||||
It looks like this fails if there is a branch named HEAD, but plain git status also emits warnings in this case for me, so it's probably reasonable not to support this.
When you are on a branch x, and there is also a tag x, this appears to output heads/x.
However, heads/x is also a valid branch name, so if the command outputs heads/x, I think we can't distinguish between the user being on branch heads/x and the user being on branch x, with a tag named x also existing in the working copy.
It looks like git symbolic-ref HEAD might have cleaner behavior in these cases -- I can't immediately come up with a case where it gets the wrong behavior, although we'll have to do a little more work to parse it.