/src/applications/diffusion/conduit/DiffusionInternalGitRawDiffQueryConduitAPIMethod.php

https://github.com/dannysu/phabricator · PHP · 123 lines · 80 code · 21 blank · 22 comment · 6 complexity · f5a6dbbb77e03e2411ab04a0d20db102 MD5 · raw file

  1. <?php
  2. final class DiffusionInternalGitRawDiffQueryConduitAPIMethod
  3. extends DiffusionQueryConduitAPIMethod {
  4. public function isInternalAPI() {
  5. return true;
  6. }
  7. public function getAPIMethodName() {
  8. return 'diffusion.internal.gitrawdiffquery';
  9. }
  10. public function getMethodDescription() {
  11. return pht('Internal method for getting raw diff information.');
  12. }
  13. protected function defineReturnType() {
  14. return 'string';
  15. }
  16. protected function defineCustomParamTypes() {
  17. return array(
  18. 'commit' => 'required string',
  19. );
  20. }
  21. protected function getResult(ConduitAPIRequest $request) {
  22. $drequest = $this->getDiffusionRequest();
  23. $repository = $drequest->getRepository();
  24. $commit = $request->getValue('commit');
  25. if (!$repository->isGit()) {
  26. throw new Exception(
  27. pht(
  28. 'This API method can only be called on Git repositories.'));
  29. }
  30. // Check if the commit has parents. We're testing to see whether it is the
  31. // first commit in history (in which case we must use "git log") or some
  32. // other commit (in which case we can use "git diff"). We'd rather use
  33. // "git diff" because it has the right behavior for merge commits, but
  34. // it requires the commit to have a parent that we can diff against. The
  35. // first commit doesn't, so "commit^" is not a valid ref.
  36. list($parents) = $repository->execxLocalCommand(
  37. 'log -n1 --format=%s %s',
  38. '%P',
  39. $commit);
  40. $use_log = !strlen(trim($parents));
  41. // First, get a fast raw diff without "--find-copies-harder". This flag
  42. // produces better results for moves and copies, but is explosively slow
  43. // for large changes to large repositories. See T10423.
  44. $raw = $this->getRawDiff($repository, $commit, $use_log, false);
  45. // If we got a normal-sized diff (no more than 100 modified files), we'll
  46. // try using "--find-copies-harder" to improve the output. This improved
  47. // output is mostly useful for small changes anyway.
  48. $try_harder = (substr_count($raw, "\n") <= 100);
  49. if ($try_harder) {
  50. try {
  51. $raw = $this->getRawDiff($repository, $commit, $use_log, true);
  52. } catch (Exception $ex) {
  53. // Just ignore any exception we hit, we'll use the fast output
  54. // instead.
  55. }
  56. }
  57. return $raw;
  58. }
  59. private function getRawDiff(
  60. PhabricatorRepository $repository,
  61. $commit,
  62. $use_log,
  63. $try_harder) {
  64. $flags = array(
  65. '-n1',
  66. '-M',
  67. '-C',
  68. '-B',
  69. '--raw',
  70. '-t',
  71. '--abbrev=40',
  72. );
  73. if ($try_harder) {
  74. $flags[] = '--find-copies-harder';
  75. }
  76. if ($use_log) {
  77. // This is the first commit so we need to use "log". We know it's not a
  78. // merge commit because it couldn't be merging anything, so this is safe.
  79. // NOTE: "--pretty=format: " is to disable diff output, we only want the
  80. // part we get from "--raw".
  81. $future = $repository->getLocalCommandFuture(
  82. 'log %Ls --pretty=format: %s',
  83. $flags,
  84. $commit);
  85. } else {
  86. // Otherwise, we can use "diff", which will give us output for merges.
  87. // We diff against the first parent, as this is generally the expectation
  88. // and results in sensible behavior.
  89. $future = $repository->getLocalCommandFuture(
  90. 'diff %Ls %s^1 %s',
  91. $flags,
  92. $commit,
  93. $commit);
  94. }
  95. // Don't spend more than 30 seconds generating the slower output.
  96. if ($try_harder) {
  97. $future->setTimeout(30);
  98. }
  99. list($raw) = $future->resolvex();
  100. return $raw;
  101. }
  102. }