public static function newFromRawCorpus($corpus)
 {
     $obj = new ArcanistDifferentialCommitMessage();
     $obj->rawCorpus = $corpus;
     $obj->revisionID = $obj->parseRevisionIDFromRawCorpus($corpus);
     $pattern = '/^git-svn-id:\\s*([^@]+)@(\\d+)\\s+(.*)$/m';
     $match = null;
     if (preg_match($pattern, $corpus, $match)) {
         $obj->gitSVNBaseRevision = $match[1] . '@' . $match[2];
         $obj->gitSVNBasePath = $match[1];
         $obj->gitSVNUUID = $match[3];
     }
     return $obj;
 }
 public function run()
 {
     $working_copy = $this->getWorkingCopy();
     if (!$working_copy->getProjectID()) {
         throw new ArcanistUsageException("You have installed a git pre-receive hook in a remote without an " . ".arcconfig.");
     }
     // Git repositories have special rules in pre-receive hooks. We need to
     // construct the API against the .git directory instead of the project
     // root or commands don't work properly.
     $repository_api = ArcanistGitAPI::newHookAPI($_SERVER['PWD']);
     $root = $working_copy->getProjectRoot();
     $parser = new ArcanistDiffParser();
     $mark_revisions = array();
     $stdin = file_get_contents('php://stdin');
     $commits = array_filter(explode("\n", $stdin));
     foreach ($commits as $commit) {
         list($old_ref, $new_ref, $refname) = explode(' ', $commit);
         list($log) = execx('(cd %s && git log -n1 %s)', $repository_api->getPath(), $new_ref);
         $message_log = reset($parser->parseDiff($log));
         $message = ArcanistDifferentialCommitMessage::newFromRawCorpus($message_log->getMetadata('message'));
         $revision_id = $message->getRevisionID();
         if ($revision_id) {
             $mark_revisions[] = $revision_id;
         }
         // TODO: Do commit message junk.
         $info = $repository_api->getPreReceiveHookStatus($old_ref, $new_ref);
         $paths = ipull($info, 'mask');
         $frefs = ipull($info, 'ref');
         $data = array();
         foreach ($paths as $path => $mask) {
             list($stdout) = execx('(cd %s && git cat-file blob %s)', $repository_api->getPath(), $frefs[$path]);
             $data[$path] = $stdout;
         }
         // TODO: Do commit content junk.
         $commit_name = $new_ref;
         if ($revision_id) {
             $commit_name = 'D' . $revision_id . ' (' . $commit_name . ')';
         }
         echo "[arc pre-receive] {$commit_name} OK...\n";
     }
     $conduit = $this->getConduit();
     $futures = array();
     foreach ($mark_revisions as $revision_id) {
         $futures[] = $conduit->callMethod('differential.close', array('revisionID' => $revision_id));
     }
     Futures($futures)->resolveAll();
     return 0;
 }
예제 #3
0
 /**
  * Based on the 'git show' output extracts the commit date, author,
  * subject nad Differential revision .
  * 'Differential Revision:'
  *
  * @param string message output of git show -s --format="format:%ct%n%cn%n%b"
  */
 public function parseCommitMessage($message)
 {
     $message_lines = explode("\n", trim($message));
     $this->commitTime = $message_lines[0];
     $this->commitAuthor = $message_lines[1];
     $this->commitSubject = trim($message_lines[2]);
     $this->revisionID = ArcanistDifferentialCommitMessage::newFromRawCorpus($message)->getRevisionID();
 }
예제 #4
0
 public function resolveBaseCommitRule($rule, $source)
 {
     list($type, $name) = explode(':', $rule, 2);
     switch ($type) {
         case 'git':
             $matches = null;
             if (preg_match('/^merge-base\\((.+)\\)$/', $name, $matches)) {
                 list($err, $merge_base) = $this->execManualLocal('merge-base %s HEAD', $matches[1]);
                 if (!$err) {
                     $this->setBaseCommitExplanation("it is the merge-base of '{$matches[1]}' and HEAD, as " . "specified by '{$rule}' in your {$source} 'base' " . "configuration.");
                     return trim($merge_base);
                 }
             } else {
                 if (preg_match('/^branch-unique\\((.+)\\)$/', $name, $matches)) {
                     list($err, $merge_base) = $this->execManualLocal('merge-base %s HEAD', $matches[1]);
                     if ($err) {
                         return null;
                     }
                     $merge_base = trim($merge_base);
                     list($commits) = $this->execxLocal('log --format=%C %s..HEAD --', '%H', $merge_base);
                     $commits = array_filter(explode("\n", $commits));
                     if (!$commits) {
                         return null;
                     }
                     $commits[] = $merge_base;
                     $head_branch_count = null;
                     foreach ($commits as $commit) {
                         list($branches) = $this->execxLocal('branch --contains %s', $commit);
                         $branches = array_filter(explode("\n", $branches));
                         if ($head_branch_count === null) {
                             // If this is the first commit, it's HEAD. Count how many
                             // branches it is on; we want to include commits on the same
                             // number of branches. This covers a case where this branch
                             // has sub-branches and we're running "arc diff" here again
                             // for whatever reason.
                             $head_branch_count = count($branches);
                         } else {
                             if (count($branches) > $head_branch_count) {
                                 foreach ($branches as $key => $branch) {
                                     $branches[$key] = trim($branch, ' *');
                                 }
                                 $branches = implode(', ', $branches);
                                 $this->setBaseCommitExplanation("it is the first commit between '{$merge_base}' (the " . "merge-base of '{$matches[1]}' and HEAD) which is also " . "contained by another branch ({$branches}).");
                                 return $commit;
                             }
                         }
                     }
                 } else {
                     list($err) = $this->execManualLocal('cat-file -t %s', $name);
                     if (!$err) {
                         $this->setBaseCommitExplanation("it is specified by '{$rule}' in your {$source} 'base' " . "configuration.");
                         return $name;
                     }
                 }
             }
             break;
         case 'arc':
             switch ($name) {
                 case 'empty':
                     $this->setBaseCommitExplanation("you specified '{$rule}' in your {$source} 'base' " . "configuration.");
                     return self::GIT_MAGIC_ROOT_COMMIT;
                 case 'amended':
                     $text = $this->getCommitMessage('HEAD');
                     $message = ArcanistDifferentialCommitMessage::newFromRawCorpus($text);
                     if ($message->getRevisionID()) {
                         $this->setBaseCommitExplanation("HEAD has been amended with 'Differential Revision:', " . "as specified by '{$rule}' in your {$source} 'base' " . "configuration.");
                         return 'HEAD^';
                     }
                     break;
                 case 'upstream':
                     list($err, $upstream) = $this->execManualLocal('rev-parse --abbrev-ref --symbolic-full-name %s', '@{upstream}');
                     if (!$err) {
                         $upstream = rtrim($upstream);
                         list($upstream_merge_base) = $this->execxLocal('merge-base %s HEAD', $upstream);
                         $upstream_merge_base = rtrim($upstream_merge_base);
                         $this->setBaseCommitExplanation("it is the merge-base of the upstream of the current branch " . "and HEAD, and matched the rule '{$rule}' in your {$source} " . "'base' configuration.");
                         return $upstream_merge_base;
                     }
                     break;
                 case 'this':
                     $this->setBaseCommitExplanation("you specified '{$rule}' in your {$source} 'base' " . "configuration.");
                     return 'HEAD^';
             }
         default:
             return null;
     }
     return null;
 }
예제 #5
0
 /**
  * Retrieve the git messages between HEAD and the last update.
  *
  * @task message
  */
 private function getGitUpdateMessage()
 {
     $repository_api = $this->getRepositoryAPI();
     $parser = $this->newDiffParser();
     $commit_messages = $repository_api->getGitCommitLog();
     $commit_messages = $parser->parseDiff($commit_messages);
     if (count($commit_messages) == 1) {
         // If there's only one message, assume this is an amend-based workflow and
         // that using it to prefill doesn't make sense.
         return null;
     }
     // We have more than one message, so figure out which ones are new. We
     // do this by pulling the current diff and comparing commit hashes in the
     // working copy with attached commit hashes. It's not super important that
     // we always get this 100% right, we're just trying to do something
     // reasonable.
     $local = $this->loadActiveLocalCommitInfo();
     $hashes = ipull($local, null, 'commit');
     $usable = array();
     foreach ($commit_messages as $message) {
         $text = $message->getMetadata('message');
         $parsed = ArcanistDifferentialCommitMessage::newFromRawCorpus($text);
         if ($parsed->getRevisionID()) {
             // If this is an amended commit message with a revision ID, it's
             // certainly not new. Stop marking commits as usable and break out.
             break;
         }
         if (isset($hashes[$message->getCommitHash()])) {
             // If this commit is currently part of the diff, stop using commit
             // messages, since anything older than this isn't new.
             break;
         }
         // Otherwise, this looks new, so it's a usable commit message.
         $usable[] = $text;
     }
     if (!$usable) {
         // No new commit messages, so we don't have anywhere to start from.
         return null;
     }
     return $this->formatUsableLogs($usable);
 }
예제 #6
0
 private function loadCommitInfo(array $branches, ArcanistRepositoryAPI $repository_api)
 {
     $futures = array();
     foreach ($branches as $branch) {
         // 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%b', $branch['name']);
     }
     $branches = ipull($branches, null, 'name');
     $commit_map = array();
     foreach (Futures($futures) as $name => $future) {
         list($info) = $future->resolvex();
         list($hash, $epoch, $tree, $desc, $text) = explode("", trim($info), 5);
         $branch = $branches[$name];
         $branch['hash'] = $hash;
         $branch['desc'] = $desc;
         try {
             $text = $desc . "\n" . $text;
             $message = ArcanistDifferentialCommitMessage::newFromRawCorpus($text);
             $id = $message->getRevisionID();
             $branch += array('epoch' => (int) $epoch, 'tree' => $tree, 'revisionID' => $id);
         } catch (ArcanistUsageException $ex) {
             // In case of invalid commit message which fails the parsing,
             // do nothing.
         }
         $commit_map[$hash] = $branch;
     }
     return $commit_map;
 }
예제 #7
0
 public function resolveBaseCommitRule($rule, $source)
 {
     list($type, $name) = explode(':', $rule, 2);
     switch ($type) {
         case 'git':
             $matches = null;
             if (preg_match('/^merge-base\\((.+)\\)$/', $name, $matches)) {
                 list($err, $merge_base) = $this->execManualLocal('merge-base %s HEAD', $matches[1]);
                 if (!$err) {
                     $this->setBaseCommitExplanation(pht("it is the merge-base of '%s' and HEAD, as specified by " . "'%s' in your %s 'base' configuration.", $matches[1], $rule, $source));
                     return trim($merge_base);
                 }
             } else {
                 if (preg_match('/^branch-unique\\((.+)\\)$/', $name, $matches)) {
                     list($err, $merge_base) = $this->execManualLocal('merge-base %s HEAD', $matches[1]);
                     if ($err) {
                         return null;
                     }
                     $merge_base = trim($merge_base);
                     list($commits) = $this->execxLocal('log --format=%C %s..HEAD --', '%H', $merge_base);
                     $commits = array_filter(explode("\n", $commits));
                     if (!$commits) {
                         return null;
                     }
                     $commits[] = $merge_base;
                     $head_branch_count = null;
                     $all_branch_names = ipull($this->getAllBranches(), 'name');
                     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('-c column.ui=never -c color.ui=never branch --contains %s', $commit);
                         $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 this is the first commit, it's HEAD. Count how many
                             // branches it is on; we want to include commits on the same
                             // number of branches. This covers a case where this branch
                             // has sub-branches and we're running "arc diff" here again
                             // for whatever reason.
                             $head_branch_count = count($branches);
                         } else {
                             if (count($branches) > $head_branch_count) {
                                 $branches = implode(', ', $branches);
                                 $this->setBaseCommitExplanation(pht("it is the first commit between '%s' (the merge-base of " . "'%s' and HEAD) which is also contained by another branch " . "(%s).", $merge_base, $matches[1], $branches));
                                 return $commit;
                             }
                         }
                     }
                 } else {
                     list($err) = $this->execManualLocal('cat-file -t %s', $name);
                     if (!$err) {
                         $this->setBaseCommitExplanation(pht("it is specified by '%s' in your %s 'base' configuration.", $rule, $source));
                         return $name;
                     }
                 }
             }
             break;
         case 'arc':
             switch ($name) {
                 case 'empty':
                     $this->setBaseCommitExplanation(pht("you specified '%s' in your %s 'base' configuration.", $rule, $source));
                     return self::GIT_MAGIC_ROOT_COMMIT;
                 case 'amended':
                     $text = $this->getCommitMessage('HEAD');
                     $message = ArcanistDifferentialCommitMessage::newFromRawCorpus($text);
                     if ($message->getRevisionID()) {
                         $this->setBaseCommitExplanation(pht("HEAD has been amended with 'Differential Revision:', " . "as specified by '%s' in your %s 'base' configuration.", $rule, $source));
                         return 'HEAD^';
                     }
                     break;
                 case 'upstream':
                     list($err, $upstream) = $this->execManualLocal('rev-parse --abbrev-ref --symbolic-full-name %s', '@{upstream}');
                     if (!$err) {
                         $upstream = rtrim($upstream);
                         list($upstream_merge_base) = $this->execxLocal('merge-base %s HEAD', $upstream);
                         $upstream_merge_base = rtrim($upstream_merge_base);
                         $this->setBaseCommitExplanation(pht("it is the merge-base of the upstream of the current branch " . "and HEAD, and matched the rule '%s' in your %s " . "'base' configuration.", $rule, $source));
                         return $upstream_merge_base;
                     }
                     break;
                 case 'this':
                     $this->setBaseCommitExplanation(pht("you specified '%s' in your %s 'base' configuration.", $rule, $source));
                     return 'HEAD^';
             }
         default:
             return null;
     }
     return null;
 }
 private function shouldAmend()
 {
     $api = $this->getRepositoryAPI();
     if ($this->isHistoryImmutable() || !$api->supportsAmend()) {
         return false;
     }
     $commits = $api->getLocalCommitInformation();
     if (!$commits) {
         return false;
     }
     $commit = reset($commits);
     $message = ArcanistDifferentialCommitMessage::newFromRawCorpus($commit['message']);
     if ($message->getGitSVNBaseRevision()) {
         return false;
     }
     if ($api->getAuthor() != $commit['author']) {
         return false;
     }
     if ($message->getRevisionID() && $this->getArgument('create')) {
         return false;
     }
     // TODO: Check commits since tracking branch. If empty then return false.
     $repository = $this->loadProjectRepository();
     if ($repository) {
         $callsign = $repository['callsign'];
         $known_commits = $this->getConduit()->callMethodSynchronous('diffusion.getcommits', array('commits' => array('r' . $callsign . $commit['commit'])));
         if (ifilter($known_commits, 'error', $negate = true)) {
             return false;
         }
     }
     if (!$message->getRevisionID()) {
         return true;
     }
     $in_working_copy = $api->loadWorkingCopyDifferentialRevisions($this->getConduit(), array('authors' => array($this->getUserPHID()), 'status' => 'status-open'));
     if ($in_working_copy) {
         return true;
     }
     return false;
 }
예제 #9
0
 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;
 }
예제 #10
0
 private function calculateShouldAmend()
 {
     $api = $this->getRepositoryAPI();
     if ($this->isHistoryImmutable() || !$api->supportsAmend()) {
         return false;
     }
     $commits = $api->getLocalCommitInformation();
     if (!$commits) {
         return false;
     }
     $commit = reset($commits);
     $message = ArcanistDifferentialCommitMessage::newFromRawCorpus($commit['message']);
     if ($message->getGitSVNBaseRevision()) {
         return false;
     }
     if ($api->getAuthor() != $commit['author']) {
         return false;
     }
     if ($message->getRevisionID() && $this->getArgument('create')) {
         return false;
     }
     // TODO: Check commits since tracking branch. If empty then return false.
     // Don't amend the current commit if it has already been published.
     $repository = $this->loadProjectRepository();
     if ($repository) {
         $callsign = $repository['callsign'];
         $commit_name = 'r' . $callsign . $commit['commit'];
         $result = $this->getConduit()->callMethodSynchronous('diffusion.querycommits', array('names' => array($commit_name)));
         $known_commit = idx($result['identifierMap'], $commit_name);
         if ($known_commit) {
             return false;
         }
     }
     if (!$message->getRevisionID()) {
         return true;
     }
     $in_working_copy = $api->loadWorkingCopyDifferentialRevisions($this->getConduit(), array('authors' => array($this->getUserPHID()), 'status' => 'status-open'));
     if ($in_working_copy) {
         return true;
     }
     return false;
 }
예제 #11
0
 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;
 }
예제 #12
0
 public function resolveBaseCommitRule($rule, $source)
 {
     list($type, $name) = explode(':', $rule, 2);
     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("it is the greatest common ancestor of '{$matches[1]}' and ., as" . "specified by '{$rule}' in your {$source} 'base' " . "configuration.");
                     return trim($merge_base);
                 }
             } else {
                 list($err) = $this->execManualLocal('id -r %s', $name);
                 if (!$err) {
                     $this->setBaseCommitExplanation("it is specified by '{$rule}' in your {$source} 'base' " . "configuration.");
                     return $name;
                 }
             }
             break;
         case 'arc':
             switch ($name) {
                 case 'empty':
                     $this->setBaseCommitExplanation("you specified '{$rule}' in your {$source} 'base' " . "configuration.");
                     return 'null';
                 case 'outgoing':
                     list($err, $outgoing_base) = $this->execManualLocal('log --template={node} --rev %s', 'limit(reverse(ancestors(.) - outgoing()), 1)');
                     if (!$err) {
                         $this->setBaseCommitExplanation("it is the first ancestor of the working copy that is not " . "outgoing, and it matched the rule {$rule} in your {$source} " . "'base' configuration.");
                         return trim($outgoing_base);
                     }
                 case 'amended':
                     $text = $this->getCommitMessage('.');
                     $message = ArcanistDifferentialCommitMessage::newFromRawCorpus($text);
                     if ($message->getRevisionID()) {
                         $this->setBaseCommitExplanation("'.' has been amended with 'Differential Revision:', " . "as specified by '{$rule}' in your {$source} 'base' " . "configuration.");
                         // NOTE: This should be safe because Mercurial doesn't support
                         // amend until 2.2.
                         return '.^';
                     }
                     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("it is the first ancestor of . that either has a bookmark, or " . "is already in the remote and it matched the rule {$rule} in " . "your {$source} 'base' configuration");
                         return trim($bookmark_base);
                     }
             }
             break;
         default:
             return null;
     }
     return null;
 }
예제 #13
0
 public function loadWorkingCopyDifferentialRevisions(ConduitClient $conduit, array $query)
 {
     $messages = $this->getGitCommitLog();
     if (!strlen($messages)) {
         return array();
     }
     $parser = new ArcanistDiffParser();
     $messages = $parser->parseDiff($messages);
     // First, try to find revisions by explicit revision IDs in commit messages.
     $revision_ids = array();
     foreach ($messages as $message) {
         $object = ArcanistDifferentialCommitMessage::newFromRawCorpus($message->getMetadata('message'));
         if ($object->getRevisionID()) {
             $revision_ids[] = $object->getRevisionID();
         }
     }
     if ($revision_ids) {
         $results = $conduit->callMethodSynchronous('differential.query', $query + array('ids' => $revision_ids));
         return $results;
     }
     // If we didn't succeed, try to find revisions by hash.
     $hashes = array();
     foreach ($this->getLocalCommitInformation() as $commit) {
         $hashes[] = array('gtcm', $commit['commit']);
         $hashes[] = array('gttr', $commit['tree']);
     }
     $results = $conduit->callMethodSynchronous('differential.query', $query + array('commitHashes' => $hashes));
     return $results;
 }
예제 #14
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;
 }