PageRenderTime 44ms CodeModel.GetById 17ms RepoModel.GetById 0ms app.codeStats 0ms

/src/applications/repository/worker/commitchangeparser/git/PhabricatorRepositoryGitCommitChangeParserWorker.php

http://github.com/facebook/phabricator
PHP | 256 lines | 190 code | 36 blank | 30 comment | 15 complexity | 6999b2d7d0f26038cd7fcf9bb14bc26f MD5 | raw file
Possible License(s): JSON, MPL-2.0-no-copyleft-exception, Apache-2.0, BSD-3-Clause, LGPL-2.0, MIT, LGPL-2.1, LGPL-3.0
  1. <?php
  2. /*
  3. * Copyright 2011 Facebook, Inc.
  4. *
  5. * Licensed under the Apache License, Version 2.0 (the "License");
  6. * you may not use this file except in compliance with the License.
  7. * You may obtain a copy of the License at
  8. *
  9. * http://www.apache.org/licenses/LICENSE-2.0
  10. *
  11. * Unless required by applicable law or agreed to in writing, software
  12. * distributed under the License is distributed on an "AS IS" BASIS,
  13. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. * See the License for the specific language governing permissions and
  15. * limitations under the License.
  16. */
  17. class PhabricatorRepositoryGitCommitChangeParserWorker
  18. extends PhabricatorRepositoryCommitChangeParserWorker {
  19. protected function parseCommit(
  20. PhabricatorRepository $repository,
  21. PhabricatorRepositoryCommit $commit) {
  22. $full_name = 'r'.$repository->getCallsign().$commit->getCommitIdentifier();
  23. echo "Parsing {$full_name}...\n";
  24. if ($this->isBadCommit($full_name)) {
  25. echo "This commit is marked bad!\n";
  26. return;
  27. }
  28. // NOTE: "--pretty=format: " is to disable log output, we only want the
  29. // part we get from "--raw".
  30. list($raw) = $repository->execxLocalCommand(
  31. 'log -n1 -M -C -B --find-copies-harder --raw -t '.
  32. '--abbrev=40 --pretty=format: %s',
  33. $commit->getCommitIdentifier());
  34. $changes = array();
  35. $move_away = array();
  36. $copy_away = array();
  37. $lines = explode("\n", $raw);
  38. foreach ($lines as $line) {
  39. if (!strlen(trim($line))) {
  40. continue;
  41. }
  42. list($old_mode, $new_mode,
  43. $old_hash, $new_hash,
  44. $more_stuff) = preg_split('/ +/', $line);
  45. // We may only have two pieces here.
  46. list($action, $src_path, $dst_path) = array_merge(
  47. explode("\t", $more_stuff),
  48. array(null));
  49. // Normalize the paths for consistency with the SVN workflow.
  50. $src_path = '/'.$src_path;
  51. if ($dst_path) {
  52. $dst_path = '/'.$dst_path;
  53. }
  54. $old_mode = intval($old_mode, 8);
  55. $new_mode = intval($new_mode, 8);
  56. $file_type = DifferentialChangeType::FILE_NORMAL;
  57. if ($new_mode & 040000) {
  58. $file_type = DifferentialChangeType::FILE_DIRECTORY;
  59. } else if ($new_mode & 0120000) {
  60. $file_type = DifferentialChangeType::FILE_SYMLINK;
  61. }
  62. // TODO: We can detect binary changes as git does, through a combination
  63. // of running 'git check-attr' for stuff like 'binary', 'merge' or 'diff',
  64. // and by falling back to inspecting the first 8,000 characters of the
  65. // buffer for null bytes (this is seriously git's algorithm, see
  66. // buffer_is_binary() in xdiff-interface.c).
  67. $change_type = null;
  68. $change_path = $src_path;
  69. $change_target = null;
  70. $is_direct = true;
  71. switch ($action[0]) {
  72. case 'A':
  73. $change_type = DifferentialChangeType::TYPE_ADD;
  74. break;
  75. case 'D':
  76. $change_type = DifferentialChangeType::TYPE_DELETE;
  77. break;
  78. case 'C':
  79. $change_type = DifferentialChangeType::TYPE_COPY_HERE;
  80. $change_path = $dst_path;
  81. $change_target = $src_path;
  82. $copy_away[$change_target][] = $change_path;
  83. break;
  84. case 'R':
  85. $change_type = DifferentialChangeType::TYPE_MOVE_HERE;
  86. $change_path = $dst_path;
  87. $change_target = $src_path;
  88. $move_away[$change_target][] = $change_path;
  89. break;
  90. case 'T':
  91. // Type of the file changed, fall through and treat it as a
  92. // modification. Not 100% sure this is the right thing to do but it
  93. // seems reasonable.
  94. case 'M':
  95. if ($file_type == DifferentialChangeType::FILE_DIRECTORY) {
  96. $change_type = DifferentialChangeType::TYPE_CHILD;
  97. $is_direct = false;
  98. } else {
  99. $change_type = DifferentialChangeType::TYPE_CHANGE;
  100. }
  101. break;
  102. // NOTE: "U" (unmerged) and "X" (unknown) statuses are also possible
  103. // in theory but shouldn't appear here.
  104. default:
  105. throw new Exception("Failed to parse line '{$line}'.");
  106. }
  107. $changes[$change_path] = array(
  108. 'repositoryID' => $repository->getID(),
  109. 'commitID' => $commit->getID(),
  110. 'path' => $change_path,
  111. 'changeType' => $change_type,
  112. 'fileType' => $file_type,
  113. 'isDirect' => $is_direct,
  114. 'commitSequence' => $commit->getEpoch(),
  115. 'targetPath' => $change_target,
  116. 'targetCommitID' => $change_target ? $commit->getID() : null,
  117. );
  118. }
  119. // Add a change to '/' since git doesn't mention it.
  120. $changes['/'] = array(
  121. 'repositoryID' => $repository->getID(),
  122. 'commitID' => $commit->getID(),
  123. 'path' => '/',
  124. 'changeType' => DifferentialChangeType::TYPE_CHILD,
  125. 'fileType' => DifferentialChangeType::FILE_DIRECTORY,
  126. 'isDirect' => false,
  127. 'commitSequence' => $commit->getEpoch(),
  128. 'targetPath' => null,
  129. 'targetCommitID' => null,
  130. );
  131. foreach ($copy_away as $change_path => $destinations) {
  132. if (isset($move_away[$change_path])) {
  133. $change_type = DifferentialChangeType::TYPE_MULTICOPY;
  134. $is_direct = true;
  135. unset($move_away[$change_path]);
  136. } else {
  137. $change_type = DifferentialChangeType::TYPE_COPY_AWAY;
  138. $is_direct = false;
  139. }
  140. $reference = $changes[reset($destinations)];
  141. $changes[$change_path] = array(
  142. 'repositoryID' => $repository->getID(),
  143. 'commitID' => $commit->getID(),
  144. 'path' => $change_path,
  145. 'changeType' => $change_type,
  146. 'fileType' => $reference['fileType'],
  147. 'isDirect' => $is_direct,
  148. 'commitSequence' => $commit->getEpoch(),
  149. 'targetPath' => null,
  150. 'targetCommitID' => null,
  151. );
  152. }
  153. foreach ($move_away as $change_path => $destinations) {
  154. $reference = $changes[reset($destinations)];
  155. $changes[$change_path] = array(
  156. 'repositoryID' => $repository->getID(),
  157. 'commitID' => $commit->getID(),
  158. 'path' => $change_path,
  159. 'changeType' => DifferentialChangeType::TYPE_MOVE_AWAY,
  160. 'fileType' => $reference['fileType'],
  161. 'isDirect' => true,
  162. 'commitSequence' => $commit->getEpoch(),
  163. 'targetPath' => null,
  164. 'targetCommitID' => null,
  165. );
  166. }
  167. $paths = array();
  168. foreach ($changes as $change) {
  169. $paths[$change['path']] = true;
  170. if ($change['targetPath']) {
  171. $paths[$change['targetPath']] = true;
  172. }
  173. }
  174. $path_map = $this->lookupOrCreatePaths(array_keys($paths));
  175. foreach ($changes as $key => $change) {
  176. $changes[$key]['pathID'] = $path_map[$change['path']];
  177. if ($change['targetPath']) {
  178. $changes[$key]['targetPathID'] = $path_map[$change['targetPath']];
  179. } else {
  180. $changes[$key]['targetPathID'] = null;
  181. }
  182. }
  183. $conn_w = $repository->establishConnection('w');
  184. $changes_sql = array();
  185. foreach ($changes as $change) {
  186. $values = array(
  187. (int)$change['repositoryID'],
  188. (int)$change['pathID'],
  189. (int)$change['commitID'],
  190. $change['targetPathID']
  191. ? (int)$change['targetPathID']
  192. : 'null',
  193. $change['targetCommitID']
  194. ? (int)$change['targetCommitID']
  195. : 'null',
  196. (int)$change['changeType'],
  197. (int)$change['fileType'],
  198. (int)$change['isDirect'],
  199. (int)$change['commitSequence'],
  200. );
  201. $changes_sql[] = '('.implode(', ', $values).')';
  202. }
  203. queryfx(
  204. $conn_w,
  205. 'DELETE FROM %T WHERE commitID = %d',
  206. PhabricatorRepository::TABLE_PATHCHANGE,
  207. $commit->getID());
  208. foreach (array_chunk($changes_sql, 256) as $sql_chunk) {
  209. queryfx(
  210. $conn_w,
  211. 'INSERT INTO %T
  212. (repositoryID, pathID, commitID, targetPathID, targetCommitID,
  213. changeType, fileType, isDirect, commitSequence)
  214. VALUES %Q',
  215. PhabricatorRepository::TABLE_PATHCHANGE,
  216. implode(', ', $sql_chunk));
  217. }
  218. $this->finishParse();
  219. }
  220. }