protected function getMercurialResult(ConduitAPIRequest $request)
 {
     $drequest = $this->getDiffusionRequest();
     $path = $drequest->getPath();
     $grep = $request->getValue('grep');
     $repository = $drequest->getRepository();
     $limit = $request->getValue('limit');
     $offset = $request->getValue('offset');
     $results = array();
     $future = $repository->getLocalCommandFuture('grep --rev %s --print0 --line-number %s %s', hgsprintf('ancestors(%s)', $drequest->getStableCommit()), $grep, $path);
     $lines = id(new LinesOfALargeExecFuture($future))->setDelimiter("");
     $parts = array();
     foreach ($lines as $line) {
         $parts[] = $line;
         if (count($parts) == 4) {
             list($path, $char_offset, $line, $string) = $parts;
             $results[] = array($path, $line, $string);
             if (count($results) >= $offset + $limit) {
                 break;
             }
             $parts = array();
         }
     }
     unset($lines);
     return $results;
 }
 public function testhgsprintf()
 {
     $this->assertEqual("'version-1'", hgsprintf('%s', 'version-1'));
     $this->assertEqual("'single\\'quote'", hgsprintf('%s', "single'quote"));
     $this->assertEqual("'back\\\\slash'", hgsprintf('%s', 'back\\slash'));
     $this->assertEqual("'33%'", hgsprintf('%R', hgsprintf('%s', '33%')));
 }
 public function __construct(PhabricatorRepository $repository, $commit)
 {
     $this->repository = $repository;
     $future = $repository->getLocalCommandFuture('log --template %s --rev %s', '{rev}\\1{node}\\1{date}\\1{parents}\\2', hgsprintf('reverse(ancestors(%s))', $commit));
     $this->iterator = new LinesOfALargeExecFuture($future);
     $this->iterator->setDelimiter("");
     $this->iterator->rewind();
 }
 protected function executeQuery()
 {
     $repository = $this->getRepository();
     $path = $this->path;
     $commit = $this->commit;
     $match_against = trim($path, '/');
     $prefix = trim('./' . $match_against, '/');
     list($entire_manifest) = $repository->execxLocalCommand('locate --print0 --rev %s -I %s', hgsprintf('%s', $commit), $prefix);
     return explode("", $entire_manifest);
 }
 private function loadMercurialCommitRef()
 {
     $repository = $this->getRepository();
     list($stdout) = $repository->execxLocalCommand('log --template %s --rev %s', '{author}\\n{desc}', hgsprintf('%s', $this->identifier));
     list($author, $message) = explode("\n", $stdout, 2);
     $author = phutil_utf8ize($author);
     $message = phutil_utf8ize($message);
     list($author_name, $author_email) = $this->splitUserIdentifier($author);
     $hashes = array(id(new DiffusionCommitHash())->setHashType(ArcanistDifferentialRevisionHash::HASH_MERCURIAL_COMMIT)->setHashValue($this->identifier));
     return id(new DiffusionCommitRef())->setAuthorName($author_name)->setAuthorEmail($author_email)->setMessage($message)->setHashes($hashes);
 }
 protected function getMercurialResult(ConduitAPIRequest $request)
 {
     $drequest = $this->getDiffusionRequest();
     $repository = $drequest->getRepository();
     $paths = $request->getValue('paths');
     $results = $this->loadCommitsFromCache($paths);
     foreach ($paths as $path => $commit) {
         if (array_key_exists($path, $results)) {
             continue;
         }
         list($hash) = $repository->execxLocalCommand('log --template %s --limit 1 --removed --rev %s -- %s', '{node}', hgsprintf('reverse(ancestors(%s))', $commit), nonempty(ltrim($path, '/'), '.'));
         $results[$path] = trim($hash);
     }
     return $results;
 }
 protected function executeQuery()
 {
     $repository = $this->getRepository();
     $path = $this->path;
     $commit = $this->commit;
     $hg_paths_command = 'locate --print0 --rev %s -I %s';
     $hg_version = PhabricatorRepositoryVersion::getMercurialVersion();
     if (PhabricatorRepositoryVersion::isMercurialFilesCommandAvailable($hg_version)) {
         $hg_paths_command = 'files --print0 --rev %s -I %s';
     }
     $match_against = trim($path, '/');
     $prefix = trim('./' . $match_against, '/');
     list($entire_manifest) = $repository->execxLocalCommand($hg_paths_command, hgsprintf('%s', $commit), $prefix);
     return explode("", $entire_manifest);
 }
 protected function executeQuery()
 {
     $repository = $this->getRepository();
     if ($this->contains !== null) {
         $spec = hgsprintf('(descendants(%s) and head())', $this->contains);
     } else {
         $spec = hgsprintf('head()');
     }
     list($stdout) = $repository->execxLocalCommand('log --template %s --rev %s', '{node}\\1{branch}\\2', $spec);
     $branches = array();
     $lines = explode("", $stdout);
     $lines = array_filter($lines);
     foreach ($lines as $line) {
         list($node, $branch) = explode("", $line);
         $branches[] = id(new DiffusionRepositoryRef())->setShortName($branch)->setCommitIdentifier($node);
     }
     return $branches;
 }
 protected function executeQuery()
 {
     $repository = $this->getRepository();
     $specs = array();
     if ($this->contains !== null) {
         $specs['all'] = hgsprintf('(descendants(%s) and head())', $this->contains);
         $specs['open'] = hgsprintf('(descendants(%s) and head() and not closed())', $this->contains);
     } else {
         $specs['all'] = hgsprintf('head()');
         $specs['open'] = hgsprintf('head() and not closed()');
     }
     $futures = array();
     foreach ($specs as $key => $spec) {
         $futures[$key] = $repository->getLocalCommandFuture('log --template %s --rev %s', '{node}\\1{branch}\\2', $spec);
     }
     $branches = array();
     $open = array();
     foreach (new FutureIterator($futures) as $key => $future) {
         list($stdout) = $future->resolvex();
         $lines = explode("", $stdout);
         $lines = array_filter($lines);
         foreach ($lines as $line) {
             list($node, $branch) = explode("", $line);
             $id = $node . '/' . $branch;
             if (empty($branches[$id])) {
                 $branches[$id] = id(new DiffusionRepositoryRef())->setShortName($branch)->setCommitIdentifier($node);
             }
             if ($key == 'open') {
                 $open[$id] = true;
             }
         }
     }
     foreach ($branches as $id => $branch) {
         $branch->setRawFields(array('closed' => empty($open[$id])));
     }
     return array_values($branches);
 }
 private function loadCommitInfo(array $branches)
 {
     $repository_api = $this->getRepositoryAPI();
     $futures = array();
     foreach ($branches as $branch) {
         if ($repository_api instanceof ArcanistMercurialAPI) {
             $futures[$branch['name']] = $repository_api->execFutureLocal('log -l 1 --template %s -r %s', "{node}{date|hgdate}{p1node}{desc|firstline}{desc}", hgsprintf('%s', $branch['name']));
         } else {
             // NOTE: "-s" is an option deep in git's diff argument parser that
             // doesn't seem to have much documentation and has no long form. It
             // suppresses any diff output.
             $futures[$branch['name']] = $repository_api->execFutureLocal('show -s --format=%C %s --', '%H%x01%ct%x01%T%x01%s%x01%s%n%n%b', $branch['name']);
         }
     }
     $branches = ipull($branches, null, 'name');
     $futures = id(new FutureIterator($futures))->limit(16);
     foreach ($futures as $name => $future) {
         list($info) = $future->resolvex();
         list($hash, $epoch, $tree, $desc, $text) = explode("", trim($info), 5);
         $branch = $branches[$name] + array('hash' => $hash, 'desc' => $desc, 'tree' => $tree, 'epoch' => (int) $epoch);
         try {
             $message = ArcanistDifferentialCommitMessage::newFromRawCorpus($text);
             $id = $message->getRevisionID();
             $branch['revisionID'] = $id;
         } catch (ArcanistUsageException $ex) {
             // In case of invalid commit message which fails the parsing,
             // do nothing.
             $branch['revisionID'] = null;
         }
         $branches[$name] = $branch;
     }
     return $branches;
 }
 private function cleanupBranch()
 {
     $repository_api = $this->getRepositoryAPI();
     echo "Cleaning up feature {$this->branchType}...\n";
     if ($this->isGit) {
         list($ref) = $repository_api->execxLocal('rev-parse --verify %s', $this->branch);
         $ref = trim($ref);
         $recovery_command = csprintf('git checkout -b %s %s', $this->branch, $ref);
         echo "(Use `{$recovery_command}` if you want it back.)\n";
         $repository_api->execxLocal('branch -D %s', $this->branch);
     } else {
         if ($this->isHg) {
             $common_ancestor = $repository_api->getCanonicalRevisionName(hgsprintf("ancestor(%s,%s)", $this->onto, $this->branch));
             $branch_root = $repository_api->getCanonicalRevisionName(hgsprintf("first((%s::%s)-%s)", $common_ancestor, $this->branch, $common_ancestor));
             $repository_api->execxLocal('--config extensions.mq= strip -r %s', $branch_root);
             if ($repository_api->isBookmark($this->branch)) {
                 $repository_api->execxLocal('bookmark -d %s', $this->branch);
             }
         }
     }
     if ($this->getArgument('delete-remote')) {
         if ($this->isGit) {
             list($err, $ref) = $repository_api->execManualLocal('rev-parse --verify %s/%s', $this->remote, $this->branch);
             if ($err) {
                 echo "No remote feature {$this->branchType} to clean up.\n";
             } else {
                 // NOTE: In Git, you delete a remote branch by pushing it with a
                 // colon in front of its name:
                 //
                 //   git push <remote> :<branch>
                 echo "Cleaning up remote feature branch...\n";
                 $repository_api->execxLocal('push %s :%s', $this->remote, $this->branch);
             }
         } else {
             if ($this->isHg) {
                 // named branches were closed as part of the earlier commit
                 // so only worry about bookmarks
                 if ($repository_api->isBookmark($this->branch)) {
                     $repository_api->execxLocal('push -B %s %s', $this->branch, $this->remote);
                 }
             }
         }
     }
 }
 /**
  * Find all ancestors of a new closing branch head which are not ancestors
  * of any old closing branch head.
  */
 private function loadNewCommitIdentifiers($new_head, array $all_closing_heads)
 {
     $repository = $this->getRepository();
     $vcs = $repository->getVersionControlSystem();
     switch ($vcs) {
         case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL:
             if ($all_closing_heads) {
                 $escheads = array();
                 foreach ($all_closing_heads as $head) {
                     $escheads[] = hgsprintf('%s', $head);
                 }
                 $escheads = implode(' or ', $escheads);
                 list($stdout) = $this->getRepository()->execxLocalCommand('log --template %s --rev %s', '{node}\\n', hgsprintf('%s', $new_head) . ' - (' . $escheads . ')');
             } else {
                 list($stdout) = $this->getRepository()->execxLocalCommand('log --template %s --rev %s', '{node}\\n', hgsprintf('%s', $new_head));
             }
             $stdout = trim($stdout);
             if (!strlen($stdout)) {
                 return array();
             }
             return phutil_split_lines($stdout, $retain_newlines = false);
         case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT:
             if ($all_closing_heads) {
                 list($stdout) = $this->getRepository()->execxLocalCommand('log --format=%s %s --not %Ls', '%H', $new_head, $all_closing_heads);
             } else {
                 list($stdout) = $this->getRepository()->execxLocalCommand('log --format=%s %s', '%H', $new_head);
             }
             $stdout = trim($stdout);
             if (!strlen($stdout)) {
                 return array();
             }
             return phutil_split_lines($stdout, $retain_newlines = false);
         default:
             throw new Exception(pht('Unsupported VCS "%s"!', $vcs));
     }
 }
 public function resolveBaseCommitRule($rule, $source)
 {
     list($type, $name) = explode(':', $rule, 2);
     // NOTE: This function MUST return node hashes or symbolic commits (like
     // branch names or the word "tip"), not revsets. This includes ".^" and
     // similar, which a revset, not a symbolic commit identifier. If you return
     // a revset it will be escaped later and looked up literally.
     switch ($type) {
         case 'hg':
             $matches = null;
             if (preg_match('/^gca\\((.+)\\)$/', $name, $matches)) {
                 list($err, $merge_base) = $this->execManualLocal('log --template={node} --rev %s', sprintf('ancestor(., %s)', $matches[1]));
                 if (!$err) {
                     $this->setBaseCommitExplanation(pht("it is the greatest common ancestor of '%s' and %s, as " . "specified by '%s' in your %s 'base' configuration.", $matches[1], '.', $rule, $source));
                     return trim($merge_base);
                 }
             } else {
                 list($err, $commit) = $this->execManualLocal('log --template {node} --rev %s', hgsprintf('%s', $name));
                 if ($err) {
                     list($err, $commit) = $this->execManualLocal('log --template {node} --rev %s', $name);
                 }
                 if (!$err) {
                     $this->setBaseCommitExplanation(pht("it is specified by '%s' in your %s 'base' configuration.", $rule, $source));
                     return trim($commit);
                 }
             }
             break;
         case 'arc':
             switch ($name) {
                 case 'empty':
                     $this->setBaseCommitExplanation(pht("you specified '%s' in your %s 'base' configuration.", $rule, $source));
                     return 'null';
                 case 'outgoing':
                     list($err, $outgoing_base) = $this->execManualLocal('log --template={node} --rev %s', 'limit(reverse(ancestors(.) - outgoing()), 1)');
                     if (!$err) {
                         $this->setBaseCommitExplanation(pht("it is the first ancestor of the working copy that is not " . "outgoing, and it matched the rule %s in your %s " . "'base' configuration.", $rule, $source));
                         return trim($outgoing_base);
                     }
                 case 'amended':
                     $text = $this->getCommitMessage('.');
                     $message = ArcanistDifferentialCommitMessage::newFromRawCorpus($text);
                     if ($message->getRevisionID()) {
                         $this->setBaseCommitExplanation(pht("'%s' has been amended with 'Differential Revision:', " . "as specified by '%s' in your %s 'base' configuration.", '.' . $rule, $source));
                         // NOTE: This should be safe because Mercurial doesn't support
                         // amend until 2.2.
                         return $this->getCanonicalRevisionName('.^');
                     }
                     break;
                 case 'bookmark':
                     $revset = 'limit(' . '  sort(' . '    (ancestors(.) and bookmark() - .) or' . '    (ancestors(.) - outgoing()), ' . '  -rev),' . '1)';
                     list($err, $bookmark_base) = $this->execManualLocal('log --template={node} --rev %s', $revset);
                     if (!$err) {
                         $this->setBaseCommitExplanation(pht("it is the first ancestor of %s that either has a bookmark, " . "or is already in the remote and it matched the rule %s in " . "your %s 'base' configuration", '.', $rule, $source));
                         return trim($bookmark_base);
                     }
                     break;
                 case 'this':
                     $this->setBaseCommitExplanation(pht("you specified '%s' in your %s 'base' configuration.", $rule, $source));
                     return $this->getCanonicalRevisionName('.^');
                 default:
                     if (preg_match('/^nodiff\\((.+)\\)$/', $name, $matches)) {
                         list($results) = $this->execxLocal('log --template %s --rev %s', "{node}{desc}", sprintf('ancestor(.,%s)::.^', $matches[1]));
                         $results = array_reverse(explode("", trim($results)));
                         foreach ($results as $result) {
                             if (empty($result)) {
                                 continue;
                             }
                             list($node, $desc) = explode("", $result, 2);
                             $message = ArcanistDifferentialCommitMessage::newFromRawCorpus($desc);
                             if ($message->getRevisionID()) {
                                 $this->setBaseCommitExplanation(pht("it is the first ancestor of %s that has a diff and is " . "the gca or a descendant of the gca with '%s', " . "specified by '%s' in your %s 'base' configuration.", '.', $matches[1], $rule, $source));
                                 return $node;
                             }
                         }
                     }
                     break;
             }
             break;
         default:
             return null;
     }
     return null;
 }
 private function resolveMercurialRefs()
 {
     $repository = $this->getRepository();
     $futures = array();
     foreach ($this->refs as $ref) {
         $futures[$ref] = $repository->getLocalCommandFuture('log --template=%s --rev %s', '{node}', hgsprintf('%s', $ref));
     }
     $results = array();
     foreach (Futures($futures) as $ref => $future) {
         try {
             list($stdout) = $future->resolvex();
         } catch (CommandException $ex) {
             if (preg_match('/ambiguous identifier/', $ex->getStdErr())) {
                 // This indicates that the ref ambiguously matched several things.
                 // Eventually, it would be nice to return all of them, but it is
                 // unclear how to best do that. For now, treat it as a miss instead.
                 continue;
             }
             throw $ex;
         }
         // It doesn't look like we can figure out the type (commit/branch/rev)
         // from this output very easily. For now, just call everything a commit.
         $type = 'commit';
         $results[$ref][] = array('type' => $type, 'identifier' => trim($stdout));
     }
     return $results;
 }
 private function findMercurialPushKeyRefUpdates()
 {
     $key_namespace = getenv('HG_NAMESPACE');
     if ($key_namespace === 'phases') {
         // Mercurial changes commit phases as part of normal push operations. We
         // just ignore these, as they don't seem to represent anything
         // interesting.
         return array();
     }
     $key_name = getenv('HG_KEY');
     $key_old = getenv('HG_OLD');
     if (!strlen($key_old)) {
         $key_old = null;
     }
     $key_new = getenv('HG_NEW');
     if (!strlen($key_new)) {
         $key_new = null;
     }
     if ($key_namespace !== 'bookmarks') {
         throw new Exception(pht("Unknown Mercurial key namespace '%s', with key '%s' (%s -> %s). " . "Rejecting push.", $key_namespace, $key_name, coalesce($key_old, pht('null')), coalesce($key_new, pht('null'))));
     }
     if ($key_old === $key_new) {
         // We get a callback when the bookmark doesn't change. Just ignore this,
         // as it's a no-op.
         return array();
     }
     $ref_flags = 0;
     $merge_base = null;
     if ($key_old === null) {
         $ref_flags |= PhabricatorRepositoryPushLog::CHANGEFLAG_ADD;
     } else {
         if ($key_new === null) {
             $ref_flags |= PhabricatorRepositoryPushLog::CHANGEFLAG_DELETE;
         } else {
             list($merge_base_raw) = $this->getRepository()->execxLocalCommand('log --template %s --rev %s', '{node}', hgsprintf('ancestor(%s, %s)', $key_old, $key_new));
             if (strlen(trim($merge_base_raw))) {
                 $merge_base = trim($merge_base_raw);
             }
             if ($merge_base && $merge_base === $key_old) {
                 $ref_flags |= PhabricatorRepositoryPushLog::CHANGEFLAG_APPEND;
             } else {
                 $ref_flags |= PhabricatorRepositoryPushLog::CHANGEFLAG_REWRITE;
             }
         }
     }
     $ref_update = $this->newPushLog()->setRefType(PhabricatorRepositoryPushLog::REFTYPE_BOOKMARK)->setRefName($key_name)->setRefOld(coalesce($key_old, self::EMPTY_HASH))->setRefNew(coalesce($key_new, self::EMPTY_HASH))->setChangeFlags($ref_flags);
     return array($ref_update);
 }
 public function run()
 {
     $console = PhutilConsole::getConsole();
     if (!$this->getArgument('show-base')) {
         $this->printRepositorySection();
         $console->writeOut("\n");
     }
     $repository_api = $this->getRepositoryAPI();
     $arg_commit = $this->getArgument('commit');
     if (count($arg_commit)) {
         $this->parseBaseCommitArgument($arg_commit);
     }
     $arg = $arg_commit ? ' ' . head($arg_commit) : '';
     $repository_api->setBaseCommitArgumentRules($this->getArgument('base', ''));
     $supports_ranges = $repository_api->supportsCommitRanges();
     $head_commit = $this->getArgument('head');
     if ($head_commit !== null) {
         $arg .= csprintf(' --head %R', $head_commit);
         $repository_api->setHeadCommit($head_commit);
     }
     if ($supports_ranges) {
         $relative = $repository_api->getBaseCommit();
         if ($this->getArgument('show-base')) {
             echo $relative . "\n";
             return 0;
         }
         $info = $repository_api->getLocalCommitInformation();
         if ($info) {
             $commits = array();
             foreach ($info as $commit) {
                 $hash = substr($commit['commit'], 0, 16);
                 $summary = $commit['summary'];
                 $commits[] = "    {$hash}  {$summary}";
             }
             $commits = implode("\n", $commits);
         } else {
             $commits = '    ' . pht('(No commits.)');
         }
         $explanation = $repository_api->getBaseCommitExplanation();
         $relative_summary = $repository_api->getCommitSummary($relative);
         $relative = substr($relative, 0, 16);
         if ($repository_api instanceof ArcanistGitAPI) {
             $head = $this->getArgument('head', 'HEAD');
             $command = csprintf('git diff %R', "{$relative}..{$head}");
         } else {
             if ($repository_api instanceof ArcanistMercurialAPI) {
                 $command = csprintf('hg diff --rev %R', hgsprintf('%s', $relative));
             } else {
                 throw new Exception(pht('Unknown VCS!'));
             }
         }
         echo phutil_console_wrap(phutil_console_format("**%s**\n%s\n\n    %s  %s\n\n", pht('COMMIT RANGE'), pht("If you run '%s', changes between the commit:", "arc diff{$arg}"), $relative, $relative_summary));
         if ($head_commit === null) {
             $will_be_sent = pht('...and the current working copy state will be sent to ' . 'Differential, because %s', $explanation);
         } else {
             $will_be_sent = pht('...and "%s" will be sent to Differential, because %s', $head_commit, $explanation);
         }
         echo phutil_console_wrap(phutil_console_format("%s\n\n%s\n\n    \$ %s\n\n%s\n\n", $will_be_sent, pht('You can see the exact changes that will be sent by running ' . 'this command:'), $command, pht('These commits will be included in the diff:')));
         echo $commits . "\n\n\n";
     }
     $any_status = $this->getArgument('any-status');
     $query = array('status' => $any_status ? 'status-any' : 'status-open');
     $revisions = $repository_api->loadWorkingCopyDifferentialRevisions($this->getConduit(), $query);
     echo phutil_console_wrap(phutil_console_format("**%s**\n%s\n\n", pht('MATCHING REVISIONS'), pht('These Differential revisions match the changes in this working ' . 'copy:')));
     if (empty($revisions)) {
         echo "    " . pht('(No revisions match.)') . "\n";
         echo "\n";
         echo phutil_console_wrap(phutil_console_format(pht("Since there are no revisions in Differential which match this " . "working copy, a new revision will be **created** if you run " . "'%s'.\n\n", "arc diff{$arg}")));
     } else {
         $other_author_phids = array();
         foreach ($revisions as $revision) {
             if ($revision['authorPHID'] != $this->getUserPHID()) {
                 $other_author_phids[] = $revision['authorPHID'];
             }
         }
         $other_authors = array();
         if ($other_author_phids) {
             $other_authors = $this->getConduit()->callMethodSynchronous('user.query', array('phids' => $other_author_phids));
             $other_authors = ipull($other_authors, 'userName', 'phid');
         }
         foreach ($revisions as $revision) {
             $title = $revision['title'];
             $monogram = 'D' . $revision['id'];
             if ($revision['authorPHID'] != $this->getUserPHID()) {
                 $author = $other_authors[$revision['authorPHID']];
                 echo pht("    %s (%s) %s\n", $monogram, $author, $title);
             } else {
                 echo pht("    %s %s\n", $monogram, $title);
             }
             echo '        ' . pht('Reason') . ': ' . $revision['why'] . "\n";
             echo "\n";
         }
         if (count($revisions) == 1) {
             echo phutil_console_wrap(phutil_console_format(pht("Since exactly one revision in Differential matches this " . "working copy, it will be **updated** if you run '%s'.", "arc diff{$arg}")));
         } else {
             echo phutil_console_wrap(pht("Since more than one revision in Differential matches this " . "working copy, you will be asked which revision you want to " . "update if you run '%s'.", "arc diff {$arg}"));
         }
         echo "\n\n";
     }
     return 0;
 }
 private function resolveMercurialRefs()
 {
     $repository = $this->getRepository();
     // First, pull all of the branch heads in the repository. Doing this in
     // bulk is much faster than querying each individual head if we're
     // checking even a small number of refs.
     $branches = id(new DiffusionLowLevelMercurialBranchesQuery())->setRepository($repository)->executeQuery();
     $branches = mgroup($branches, 'getShortName');
     $results = array();
     $unresolved = $this->refs;
     foreach ($unresolved as $key => $ref) {
         if (empty($branches[$ref])) {
             continue;
         }
         foreach ($branches[$ref] as $branch) {
             $fields = $branch->getRawFields();
             $results[$ref][] = array('type' => 'branch', 'identifier' => $branch->getCommitIdentifier(), 'closed' => idx($fields, 'closed', false));
         }
         unset($unresolved[$key]);
     }
     if (!$unresolved) {
         return $results;
     }
     // If we still have unresolved refs (which might be things like "tip"),
     // try to resolve them individually.
     $futures = array();
     foreach ($unresolved as $ref) {
         $futures[$ref] = $repository->getLocalCommandFuture('log --template=%s --rev %s', '{node}', hgsprintf('%s', $ref));
     }
     foreach (new FutureIterator($futures) as $ref => $future) {
         try {
             list($stdout) = $future->resolvex();
         } catch (CommandException $ex) {
             if (preg_match('/ambiguous identifier/', $ex->getStdErr())) {
                 // This indicates that the ref ambiguously matched several things.
                 // Eventually, it would be nice to return all of them, but it is
                 // unclear how to best do that. For now, treat it as a miss instead.
                 continue;
             }
             if (preg_match('/unknown revision/', $ex->getStdErr())) {
                 // No matches for this ref.
                 continue;
             }
             throw $ex;
         }
         // It doesn't look like we can figure out the type (commit/branch/rev)
         // from this output very easily. For now, just call everything a commit.
         $type = 'commit';
         $results[$ref][] = array('type' => $type, 'identifier' => trim($stdout));
     }
     return $results;
 }
 protected function getMercurialResult(ConduitAPIRequest $request)
 {
     $drequest = $this->getDiffusionRequest();
     $repository = $drequest->getRepository();
     $commit_hash = $request->getValue('commit');
     $path = $request->getValue('path');
     $offset = $request->getValue('offset');
     $limit = $request->getValue('limit');
     $path = DiffusionPathIDQuery::normalizePath($path);
     $path = ltrim($path, '/');
     // NOTE: Older versions of Mercurial give different results for these
     // commands (see T1268):
     //
     //   $ hg log -- ''
     //   $ hg log
     //
     // All versions of Mercurial give different results for these commands
     // (merge commits are excluded with the "." version):
     //
     //   $ hg log -- .
     //   $ hg log
     //
     // If we don't have a path component in the query, omit it from the command
     // entirely to avoid these inconsistencies.
     // NOTE: When viewing the history of a file, we don't use "-b", because
     // Mercurial stops history at the branchpoint but we're interested in all
     // ancestors. When viewing history of a branch, we do use "-b", and thus
     // stop history (this is more consistent with the Mercurial worldview of
     // branches).
     if (strlen($path)) {
         $path_arg = csprintf('-- %s', $path);
         $branch_arg = '';
     } else {
         $path_arg = '';
         // NOTE: --branch used to be called --only-branch; use -b for
         // compatibility.
         $branch_arg = csprintf('-b %s', $drequest->getBranch());
     }
     list($stdout) = $repository->execxLocalCommand('log --debug --template %s --limit %d %C --rev %s %C', '{node};{parents}\\n', $offset + $limit, $branch_arg, hgsprintf('reverse(ancestors(%s))', $commit_hash), $path_arg);
     $stdout = PhabricatorRepository::filterMercurialDebugOutput($stdout);
     $lines = explode("\n", trim($stdout));
     $lines = array_slice($lines, $offset);
     $hash_list = array();
     $parent_map = array();
     $last = null;
     foreach (array_reverse($lines) as $line) {
         list($hash, $parents) = explode(';', $line);
         $parents = trim($parents);
         if (!$parents) {
             if ($last === null) {
                 $parent_map[$hash] = array('...');
             } else {
                 $parent_map[$hash] = array($last);
             }
         } else {
             $parents = preg_split('/\\s+/', $parents);
             foreach ($parents as $parent) {
                 list($plocal, $phash) = explode(':', $parent);
                 if (!preg_match('/^0+$/', $phash)) {
                     $parent_map[$hash][] = $phash;
                 }
             }
             // This may happen for the zeroth commit in repository, both hashes
             // are "000000000...".
             if (empty($parent_map[$hash])) {
                 $parent_map[$hash] = array('...');
             }
         }
         // The rendering code expects the first commit to be "mainline", like
         // Git. Flip the order so it does the right thing.
         $parent_map[$hash] = array_reverse($parent_map[$hash]);
         $hash_list[] = $hash;
         $last = $hash;
     }
     $hash_list = array_reverse($hash_list);
     $this->parents = $parent_map;
     return DiffusionQuery::loadHistoryForCommitIdentifiers($hash_list, $drequest);
 }
 /**
  * Find all ancestors of a new closing branch head which are not ancestors
  * of any old closing branch head.
  */
 private function loadNewCommitIdentifiers($new_head, array $all_closing_heads)
 {
     $repository = $this->getRepository();
     $vcs = $repository->getVersionControlSystem();
     switch ($vcs) {
         case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL:
             if ($all_closing_heads) {
                 $parts = array();
                 foreach ($all_closing_heads as $head) {
                     $parts[] = hgsprintf('%s', $head);
                 }
                 // See T5896. Mercurial can not parse an "X or Y or ..." rev list
                 // with more than about 300 items, because it exceeds the maximum
                 // allowed recursion depth. Split all the heads into chunks of
                 // 256, and build a query like this:
                 //
                 //   ((1 or 2 or ... or 255) or (256 or 257 or ... 511))
                 //
                 // If we have more than 65535 heads, we'll do that again:
                 //
                 //   (((1 or ...) or ...) or ((65536 or ...) or ...))
                 $chunk_size = 256;
                 while (count($parts) > $chunk_size) {
                     $chunks = array_chunk($parts, $chunk_size);
                     foreach ($chunks as $key => $chunk) {
                         $chunks[$key] = '(' . implode(' or ', $chunk) . ')';
                     }
                     $parts = array_values($chunks);
                 }
                 $parts = '(' . implode(' or ', $parts) . ')';
                 list($stdout) = $this->getRepository()->execxLocalCommand('log --template %s --rev %s', '{node}\\n', hgsprintf('%s', $new_head) . ' - ' . $parts);
             } else {
                 list($stdout) = $this->getRepository()->execxLocalCommand('log --template %s --rev %s', '{node}\\n', hgsprintf('%s', $new_head));
             }
             $stdout = trim($stdout);
             if (!strlen($stdout)) {
                 return array();
             }
             return phutil_split_lines($stdout, $retain_newlines = false);
         case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT:
             if ($all_closing_heads) {
                 list($stdout) = $this->getRepository()->execxLocalCommand('log --format=%s %s --not %Ls', '%H', $new_head, $all_closing_heads);
             } else {
                 list($stdout) = $this->getRepository()->execxLocalCommand('log --format=%s %s', '%H', $new_head);
             }
             $stdout = trim($stdout);
             if (!strlen($stdout)) {
                 return array();
             }
             return phutil_split_lines($stdout, $retain_newlines = false);
         default:
             throw new Exception(pht('Unsupported VCS "%s"!', $vcs));
     }
 }
 public function run()
 {
     $source = $this->getSource();
     $param = $this->getSourceParam();
     try {
         switch ($source) {
             case self::SOURCE_PATCH:
                 if ($param == '-') {
                     $patch = @file_get_contents('php://stdin');
                     if (!strlen($patch)) {
                         throw new ArcanistUsageException(pht('Failed to read patch from stdin!'));
                     }
                 } else {
                     $patch = Filesystem::readFile($param);
                 }
                 $bundle = ArcanistBundle::newFromDiff($patch);
                 break;
             case self::SOURCE_BUNDLE:
                 $path = $this->getArgument('arcbundle');
                 $bundle = ArcanistBundle::newFromArcBundle($path);
                 break;
             case self::SOURCE_REVISION:
                 $bundle = $this->loadRevisionBundleFromConduit($this->getConduit(), $param);
                 break;
             case self::SOURCE_DIFF:
                 $bundle = $this->loadDiffBundleFromConduit($this->getConduit(), $param);
                 break;
         }
     } catch (ConduitClientException $ex) {
         if ($ex->getErrorCode() == 'ERR-INVALID-SESSION') {
             // Phabricator is not configured to allow anonymous access to
             // Differential.
             $this->authenticateConduit();
             return $this->run();
         } else {
             throw $ex;
         }
     }
     $try_encoding = nonempty($this->getArgument('encoding'), null);
     if (!$try_encoding) {
         if ($this->requiresConduit()) {
             try {
                 $try_encoding = $this->getRepositoryEncoding();
             } catch (ConduitClientException $e) {
                 $try_encoding = null;
             }
         }
     }
     if ($try_encoding) {
         $bundle->setEncoding($try_encoding);
     }
     $sanity_check = !$this->getArgument('force', false);
     // we should update the working copy before we do ANYTHING else to
     // the working copy
     if ($this->shouldUpdateWorkingCopy()) {
         $this->updateWorkingCopy();
     }
     if ($sanity_check) {
         $this->requireCleanWorkingCopy();
     }
     $repository_api = $this->getRepositoryAPI();
     $has_base_revision = $repository_api->hasLocalCommit($bundle->getBaseRevision());
     if ($this->canBranch() && ($this->shouldBranch() || $this->shouldCommit() && $has_base_revision)) {
         if ($repository_api instanceof ArcanistGitAPI) {
             $original_branch = $repository_api->getBranchName();
         } else {
             if ($repository_api instanceof ArcanistMercurialAPI) {
                 $original_branch = $repository_api->getActiveBookmark();
             }
         }
         // If we weren't on a branch, then record the ref we'll return to
         // instead.
         if ($original_branch === null) {
             if ($repository_api instanceof ArcanistGitAPI) {
                 $original_branch = $repository_api->getCanonicalRevisionName('HEAD');
             } else {
                 if ($repository_api instanceof ArcanistMercurialAPI) {
                     $original_branch = $repository_api->getCanonicalRevisionName('.');
                 }
             }
         }
         $new_branch = $this->createBranch($bundle, $has_base_revision);
     }
     if (!$has_base_revision && $this->shouldApplyDependencies()) {
         $this->applyDependencies($bundle);
     }
     if ($sanity_check) {
         $this->sanityCheck($bundle);
     }
     if ($repository_api instanceof ArcanistSubversionAPI) {
         $patch_err = 0;
         $copies = array();
         $deletes = array();
         $patches = array();
         $propset = array();
         $adds = array();
         $symlinks = array();
         $changes = $bundle->getChanges();
         foreach ($changes as $change) {
             $type = $change->getType();
             $should_patch = true;
             $filetype = $change->getFileType();
             switch ($filetype) {
                 case ArcanistDiffChangeType::FILE_SYMLINK:
                     $should_patch = false;
                     $symlinks[] = $change;
                     break;
             }
             switch ($type) {
                 case ArcanistDiffChangeType::TYPE_MOVE_AWAY:
                 case ArcanistDiffChangeType::TYPE_MULTICOPY:
                 case ArcanistDiffChangeType::TYPE_DELETE:
                     $path = $change->getCurrentPath();
                     $fpath = $repository_api->getPath($path);
                     if (!@file_exists($fpath)) {
                         $ok = phutil_console_confirm(pht("Patch deletes file '%s', but the file does not exist in " . "the working copy. Continue anyway?", $path));
                         if (!$ok) {
                             throw new ArcanistUserAbortException();
                         }
                     } else {
                         $deletes[] = $change->getCurrentPath();
                     }
                     $should_patch = false;
                     break;
                 case ArcanistDiffChangeType::TYPE_COPY_HERE:
                 case ArcanistDiffChangeType::TYPE_MOVE_HERE:
                     $path = $change->getOldPath();
                     $fpath = $repository_api->getPath($path);
                     if (!@file_exists($fpath)) {
                         $cpath = $change->getCurrentPath();
                         if ($type == ArcanistDiffChangeType::TYPE_COPY_HERE) {
                             $verbs = pht('copies');
                         } else {
                             $verbs = pht('moves');
                         }
                         $ok = phutil_console_confirm(pht("Patch %s '%s' to '%s', but source path does not exist " . "in the working copy. Continue anyway?", $verbs, $path, $cpath));
                         if (!$ok) {
                             throw new ArcanistUserAbortException();
                         }
                     } else {
                         $copies[] = array($change->getOldPath(), $change->getCurrentPath());
                     }
                     break;
                 case ArcanistDiffChangeType::TYPE_ADD:
                     $adds[] = $change->getCurrentPath();
                     break;
             }
             if ($should_patch) {
                 $cbundle = ArcanistBundle::newFromChanges(array($change));
                 $patches[$change->getCurrentPath()] = $cbundle->toUnifiedDiff();
                 $prop_old = $change->getOldProperties();
                 $prop_new = $change->getNewProperties();
                 $props = $prop_old + $prop_new;
                 foreach ($props as $key => $ignored) {
                     if (idx($prop_old, $key) !== idx($prop_new, $key)) {
                         $propset[$change->getCurrentPath()][$key] = idx($prop_new, $key);
                     }
                 }
             }
         }
         // Before we start doing anything, create all the directories we're going
         // to add files to if they don't already exist.
         foreach ($copies as $copy) {
             list($src, $dst) = $copy;
             $this->createParentDirectoryOf($dst);
         }
         foreach ($patches as $path => $patch) {
             $this->createParentDirectoryOf($path);
         }
         foreach ($adds as $add) {
             $this->createParentDirectoryOf($add);
         }
         // TODO: The SVN patch workflow likely does not work on windows because
         // of the (cd ...) stuff.
         foreach ($copies as $copy) {
             list($src, $dst) = $copy;
             passthru(csprintf('(cd %s; svn cp %s %s)', $repository_api->getPath(), ArcanistSubversionAPI::escapeFileNameForSVN($src), ArcanistSubversionAPI::escapeFileNameForSVN($dst)));
         }
         foreach ($deletes as $delete) {
             passthru(csprintf('(cd %s; svn rm %s)', $repository_api->getPath(), ArcanistSubversionAPI::escapeFileNameForSVN($delete)));
         }
         foreach ($symlinks as $symlink) {
             $link_target = $symlink->getSymlinkTarget();
             $link_path = $symlink->getCurrentPath();
             switch ($symlink->getType()) {
                 case ArcanistDiffChangeType::TYPE_ADD:
                 case ArcanistDiffChangeType::TYPE_CHANGE:
                 case ArcanistDiffChangeType::TYPE_MOVE_HERE:
                 case ArcanistDiffChangeType::TYPE_COPY_HERE:
                     execx('(cd %s && ln -sf %s %s)', $repository_api->getPath(), $link_target, $link_path);
                     break;
             }
         }
         foreach ($patches as $path => $patch) {
             $err = null;
             if ($patch) {
                 $tmp = new TempFile();
                 Filesystem::writeFile($tmp, $patch);
                 passthru(csprintf('(cd %s; patch -p0 < %s)', $repository_api->getPath(), $tmp), $err);
             } else {
                 passthru(csprintf('(cd %s; touch %s)', $repository_api->getPath(), $path), $err);
             }
             if ($err) {
                 $patch_err = max($patch_err, $err);
             }
         }
         foreach ($adds as $add) {
             passthru(csprintf('(cd %s; svn add %s)', $repository_api->getPath(), ArcanistSubversionAPI::escapeFileNameForSVN($add)));
         }
         foreach ($propset as $path => $changes) {
             foreach ($changes as $prop => $value) {
                 if ($prop == 'unix:filemode') {
                     // Setting this property also changes the file mode.
                     $prop = 'svn:executable';
                     $value = octdec($value) & 0111 ? 'on' : null;
                 }
                 if ($value === null) {
                     passthru(csprintf('(cd %s; svn propdel %s %s)', $repository_api->getPath(), $prop, ArcanistSubversionAPI::escapeFileNameForSVN($path)));
                 } else {
                     passthru(csprintf('(cd %s; svn propset %s %s %s)', $repository_api->getPath(), $prop, $value, ArcanistSubversionAPI::escapeFileNameForSVN($path)));
                 }
             }
         }
         if ($patch_err == 0) {
             echo phutil_console_format("<bg:green>** %s **</bg> %s\n", pht('OKAY'), pht('Successfully applied patch to the working copy.'));
         } else {
             echo phutil_console_format("\n\n<bg:yellow>** %s **</bg> %s\n", pht('WARNING'), pht("Some hunks could not be applied cleanly by the unix '%s' " . "utility. Your working copy may be different from the revision's " . "base, or you may be in the wrong subdirectory. You can export " . "the raw patch file using '%s', and then try to apply it by " . "fiddling with options to '%s' (particularly, %s), or manually. " . "The output above, from '%s', may be helpful in " . "figuring out what went wrong.", 'patch', 'arc export --unified', 'patch', '-p', 'patch'));
         }
         return $patch_err;
     } else {
         if ($repository_api instanceof ArcanistGitAPI) {
             $patchfile = new TempFile();
             Filesystem::writeFile($patchfile, $bundle->toGitPatch());
             $passthru = new PhutilExecPassthru('git apply --index --reject -- %s', $patchfile);
             $passthru->setCWD($repository_api->getPath());
             $err = $passthru->execute();
             if ($err) {
                 echo phutil_console_format("\n<bg:red>** %s **</bg>\n", pht('Patch Failed!'));
                 // NOTE: Git patches may fail if they change the case of a filename
                 // (for instance, from 'example.c' to 'Example.c'). As of now, Git
                 // can not apply these patches on case-insensitive filesystems and
                 // there is no way to build a patch which works.
                 throw new ArcanistUsageException(pht('Unable to apply patch!'));
             }
             // in case there were any submodule changes involved
             $repository_api->execpassthru('submodule update --init --recursive');
             if ($this->shouldCommit()) {
                 if ($bundle->getFullAuthor()) {
                     $author_cmd = csprintf('--author=%s', $bundle->getFullAuthor());
                 } else {
                     $author_cmd = '';
                 }
                 $commit_message = $this->getCommitMessage($bundle);
                 $future = $repository_api->execFutureLocal('commit -a %C -F - --no-verify', $author_cmd);
                 $future->write($commit_message);
                 $future->resolvex();
                 $verb = pht('committed');
             } else {
                 $verb = pht('applied');
             }
             if ($this->canBranch() && !$this->shouldBranch() && $this->shouldCommit() && $has_base_revision) {
                 $repository_api->execxLocal('checkout %s', $original_branch);
                 $ex = null;
                 try {
                     $repository_api->execxLocal('cherry-pick %s', $new_branch);
                 } catch (Exception $ex) {
                     // do nothing
                 }
                 $repository_api->execxLocal('branch -D %s', $new_branch);
                 if ($ex) {
                     echo phutil_console_format("\n<bg:red>** %s**</bg>\n", pht('Cherry Pick Failed!'));
                     throw $ex;
                 }
             }
             echo phutil_console_format("<bg:green>** %s **</bg> %s\n", pht('OKAY'), pht('Successfully %s patch.', $verb));
         } else {
             if ($repository_api instanceof ArcanistMercurialAPI) {
                 $future = $repository_api->execFutureLocal('import --no-commit -');
                 $future->write($bundle->toGitPatch());
                 try {
                     $future->resolvex();
                 } catch (CommandException $ex) {
                     echo phutil_console_format("\n<bg:red>** %s **</bg>\n", pht('Patch Failed!'));
                     $stderr = $ex->getStdErr();
                     if (preg_match('/case-folding collision/', $stderr)) {
                         echo phutil_console_wrap(phutil_console_format("\n<bg:yellow>** %s **</bg> %s\n", pht('WARNING'), pht("This patch may have failed because it attempts to change " . "the case of a filename (for instance, from '%s' to '%s'). " . "Mercurial cannot apply patches like this on case-insensitive " . "filesystems. You must apply this patch manually.", 'example.c', 'Example.c')));
                     }
                     throw $ex;
                 }
                 if ($this->shouldCommit()) {
                     $author = coalesce($bundle->getFullAuthor(), $bundle->getAuthorName());
                     if ($author !== null) {
                         $author_cmd = csprintf('-u %s', $author);
                     } else {
                         $author_cmd = '';
                     }
                     $commit_message = $this->getCommitMessage($bundle);
                     $future = $repository_api->execFutureLocal('commit %C -l -', $author_cmd);
                     $future->write($commit_message);
                     $future->resolvex();
                     if (!$this->shouldBranch() && $has_base_revision) {
                         $original_rev = $repository_api->getCanonicalRevisionName($original_branch);
                         $current_parent = $repository_api->getCanonicalRevisionName(hgsprintf('%s^', $new_branch));
                         $err = 0;
                         if ($original_rev != $current_parent) {
                             list($err) = $repository_api->execManualLocal('rebase --dest %s --rev %s', hgsprintf('%s', $original_branch), hgsprintf('%s', $new_branch));
                         }
                         $repository_api->execxLocal('bookmark --delete %s', $new_branch);
                         if ($err) {
                             $repository_api->execManualLocal('rebase --abort');
                             throw new ArcanistUsageException(phutil_console_format("\n<bg:red>** %s**</bg>\n", pht('Rebase onto %s failed!', $original_branch)));
                         }
                     }
                     $verb = pht('committed');
                 } else {
                     $verb = pht('applied');
                 }
                 echo phutil_console_format("<bg:green>** %s **</bg> %s\n", pht('OKAY'), pht('Successfully %s patch.', $verb));
             } else {
                 throw new Exception(pht('Unknown version control system.'));
             }
         }
     }
     return 0;
 }
 private function loadCommitInfo(array $branches)
 {
     $repository_api = $this->getRepositoryAPI();
     $branches = ipull($branches, null, 'name');
     if ($repository_api instanceof ArcanistMercurialAPI) {
         $futures = array();
         foreach ($branches as $branch) {
             $futures[$branch['name']] = $repository_api->execFutureLocal('log -l 1 --template %s -r %s', "{node}{date|hgdate}{p1node}{desc|firstline}{desc}", hgsprintf('%s', $branch['name']));
         }
         $futures = id(new FutureIterator($futures))->limit(16);
         foreach ($futures as $name => $future) {
             list($info) = $future->resolvex();
             $fields = explode("", trim($info), 5);
             list($hash, $epoch, $tree, $desc, $text) = $fields;
             $branches[$name] += array('hash' => $hash, 'desc' => $desc, 'tree' => $tree, 'epoch' => (int) $epoch, 'text' => $text);
         }
     }
     foreach ($branches as $name => $branch) {
         $text = $branch['text'];
         try {
             $message = ArcanistDifferentialCommitMessage::newFromRawCorpus($text);
             $id = $message->getRevisionID();
             $branch['revisionID'] = $id;
         } catch (ArcanistUsageException $ex) {
             // In case of invalid commit message which fails the parsing,
             // do nothing.
             $branch['revisionID'] = null;
         }
         $branches[$name] = $branch;
     }
     return $branches;
 }