Magento 2 Documentation  2.3
Documentation for Magento 2 CMS v2.3 (December 2018)
get_github_changes.php
Go to the documentation of this file.
1 <?php
2 
12 define(
13  'USAGE',
14  <<<USAGE
15  php -f get_github_changes.php --
16  --output-file="<output_file>"
17  --base-path="<base_path>"
18  --repo="<main_repo>"
19  --branch="<branch>"
20  [--file-extensions="<comma_separated_list_of_formats>"]
21 
22 USAGE
23 );
24 
25 $options = getopt('', ['output-file:', 'base-path:', 'repo:', 'file-extensions:', 'branch:']);
26 
27 $requiredOptions = ['output-file', 'base-path', 'repo', 'branch'];
29  echo USAGE;
30  exit(1);
31 }
32 
33 $fileExtensions = explode(',', isset($options['file-extensions']) ? $options['file-extensions'] : 'php');
34 
35 include_once __DIR__ . '/framework/autoload.php';
36 
37 $mainline = 'mainline_' . (string)rand(0, 9999);
39 $branches = $repo->getBranches('--remotes');
40 generateBranchesList($options['output-file'], $branches, $options['branch']);
45 
48 $additionsFile = pathinfo($options['output-file']);
49 $additionsFile = $additionsFile['dirname']
50  . DIRECTORY_SEPARATOR
51  . $additionsFile['filename']
52  . '.added.'
53  . $additionsFile['extension'];
55 
57 
65 {
67  foreach ($repo->getChangedContentFiles() as $key => $changedContentFile) {
68  $filePath = sprintf($changedFilesContentFileName, $key);
69  $oldContent = file_exists($filePath) ? file_get_contents($filePath) : '{}';
70  $oldData = json_decode($oldContent, true);
71  $data = array_merge($oldData, $changedContentFile);
72  file_put_contents($filePath, json_encode($data));
73  }
74 }
75 
83 function generateChangedFilesList($outputFile, $changedFiles)
84 {
85  $changedFilesList = fopen($outputFile, 'w');
86  foreach ($changedFiles as $file) {
87  fwrite($changedFilesList, $file . PHP_EOL);
88  }
89  fclose($changedFilesList);
90 }
91 
100 function generateBranchesList($outputFile, $branches, $branchName)
101 {
102  $branchOutputFile = str_replace('changed_files', 'branches', $outputFile);
103  $branchesList = fopen($branchOutputFile, 'w');
104  fwrite($branchesList, $branchName . PHP_EOL);
105  foreach ($branches as $branch) {
106  fwrite($branchesList, substr(strrchr($branch, '/'), 1) . PHP_EOL);
107  }
108  fclose($branchesList);
109 }
110 
118 function getChangedFiles(array $changes, array $fileExtensions)
119 {
120  $files = [];
121  foreach ($changes as $fileName) {
122  foreach ($fileExtensions as $extensions) {
123  $isFileExension = strpos($fileName, '.' . $extensions);
124  if ($isFileExension) {
125  $files[] = $fileName;
126  }
127  }
128  }
129 
130  return $files;
131 }
132 
141 function getRepo($options, $mainline)
142 {
143  $repo = new GitRepo($options['base-path']);
144  $repo->addRemote($mainline, $options['repo']);
145  $repo->fetch($mainline);
146  return $repo;
147 }
148 
155 function retrieveChangesAcrossForks($mainline, GitRepo $repo, $branchName)
156 {
157  return $repo->compareChanges($mainline, $branchName, GitRepo::CHANGE_TYPE_ALL);
158 }
159 
166 function retrieveNewFilesAcrossForks($mainline, GitRepo $repo, $branchName)
167 {
168  return $repo->compareChanges($mainline, $branchName, GitRepo::CHANGE_TYPE_ADDED);
169 }
170 
177 function cleanup($repo, $mainline)
178 {
179  $repo->removeRemote($mainline);
180 }
181 
189 function validateInput(array $options, array $requiredOptions)
190 {
191  foreach ($requiredOptions as $requiredOption) {
192  if (!isset($options[$requiredOption]) || empty($options[$requiredOption])) {
193  return false;
194  }
195  }
196  return true;
197 }
199 //@codingStandardsIgnoreStart
200 class GitRepo
201 // @codingStandardsIgnoreEnd
202 {
203  const CHANGE_TYPE_ADDED = 1;
204  const CHANGE_TYPE_MODIFIED = 2;
205  const CHANGE_TYPE_ALL = 3;
206 
212  private $workTree;
213 
217  private $remoteList = [];
218 
229  private $changedContentFiles = [];
230 
234  public function __construct($workTree)
235  {
236  if (empty($workTree) || !is_dir($workTree)) {
237  throw new UnexpectedValueException('Working tree should be a valid path to directory');
238  }
239  $this->workTree = $workTree;
240  }
241 
248  public function addRemote($alias, $url)
249  {
250  if (isset($this->remoteList[$alias])) {
251  return;
252  }
253  $this->remoteList[$alias] = $url;
254 
255  $this->call(sprintf('remote add %s %s', $alias, $url));
256  }
257 
263  public function removeRemote($alias)
264  {
265  if (isset($this->remoteList[$alias])) {
266  $this->call(sprintf('remote rm %s', $alias));
267  unset($this->remoteList[$alias]);
268  }
269  }
270 
276  public function fetch($remoteAlias)
277  {
278  if (!isset($this->remoteList[$remoteAlias])) {
279  throw new LogicException('Alias "' . $remoteAlias . '" is not defined');
280  }
281 
282  $this->call(sprintf('fetch %s', $remoteAlias));
283  }
284 
291  public function getBranches($source = '--all')
292  {
293  $result = $this->call(sprintf('branch ' . $source));
294 
295  return is_array($result) ? $result : [];
296  }
297 
306  public function compareChanges($remoteAlias, $remoteBranch, $changesType = self::CHANGE_TYPE_ALL)
307  {
308  if (!isset($this->remoteList[$remoteAlias])) {
309  throw new LogicException('Alias "' . $remoteAlias . '" is not defined');
310  }
311 
312  $result = $this->call(sprintf('log %s/%s..HEAD --name-status --oneline', $remoteAlias, $remoteBranch));
313 
314  return is_array($result)
315  ? $this->filterChangedFiles(
316  $result,
317  $remoteAlias,
318  $remoteBranch,
319  $changesType
320  )
321  : [];
322  }
323 
333  protected function filterChangedFiles(
334  array $changes,
335  $remoteAlias,
336  $remoteBranch,
337  $changesType = self::CHANGE_TYPE_ALL
338  ) {
339  $countScannedFiles = 0;
340  $changedFilesMasks = $this->buildChangedFilesMask($changesType);
341  $filteredChanges = [];
342  foreach ($changes as $fileName) {
343  $countScannedFiles++;
344  if (($countScannedFiles % 5000) == 0) {
345  echo $countScannedFiles . " files scanned so far\n";
346  }
347 
348  $changeTypeMask = $this->detectChangeTypeMask($fileName, $changedFilesMasks);
349  if (null === $changeTypeMask) {
350  continue;
351  }
352 
353  $fileName = trim(substr($fileName, strlen($changeTypeMask)));
354  if (in_array($fileName, $filteredChanges)) {
355  continue;
356  }
357 
358  $fileChanges = $this->getFileChangeDetails($fileName, $remoteAlias, $remoteBranch);
359  if (empty($fileChanges)) {
360  continue;
361  }
362 
363  if (!(isset($this->changedContentFiles[$fileName]))) {
364  $this->setChangedContentFile($fileChanges, $fileName);
365  }
366  $filteredChanges[] = $fileName;
367  }
368  echo $countScannedFiles . " files scanned\n";
369 
370  return $filteredChanges;
371  }
372 
379  private function buildChangedFilesMask(int $changesType): array
380  {
381  $changedFilesMasks = [];
382  foreach ([
383  self::CHANGE_TYPE_ADDED => "A\t",
384  self::CHANGE_TYPE_MODIFIED => "M\t",
385  ] as $changeType => $changedFilesMask) {
386  if ($changeType & $changesType) {
387  $changedFilesMasks[] = $changedFilesMask;
388  }
389  }
390  return $changedFilesMasks;
391  }
392 
401  private function detectChangeTypeMask(string $changeRecord, array $allowedMasks)
402  {
403  foreach ($allowedMasks as $mask) {
404  if (strpos($changeRecord, $mask) === 0) {
405  return $mask;
406  }
407  }
408  return null;
409  }
410 
419  private function getFileChangeDetails(string $fileName, string $remoteAlias, string $remoteBranch): array
420  {
421  if (!is_file($this->workTree . '/' . $fileName)) {
422  return [];
423  }
424 
425  $result = $this->call(
426  sprintf(
427  'diff HEAD %s/%s -- %s',
428  $remoteAlias,
429  $remoteBranch,
430  $this->workTree . '/' . $fileName
431  )
432  );
433 
434  return $result;
435  }
436 
444  private function setChangedContentFile(array $content, $fileName)
445  {
446  $changedContent = '';
448 
449  foreach ($content as $item) {
450  if (strpos($item, '---') !== 0 && strpos($item, '-') === 0 && $line = ltrim($item, '-')) {
451  $changedContent .= $line . "\n";
452  }
453  }
454  if ($changedContent !== '') {
455  $this->changedContentFiles[$extension][$fileName] = $changedContent;
456  }
457  }
458 
464  public function getChangedContentFiles()
465  {
466  return $this->changedContentFiles;
467  }
468 
475  private function call($command)
476  {
477  $gitCmd = sprintf(
478  'git --git-dir %s --work-tree %s',
479  escapeshellarg("{$this->workTree}/.git"),
480  escapeshellarg($this->workTree)
481  );
482  $tmp = sprintf('%s %s', $gitCmd, $command);
483  exec($tmp, $output);
484  return $output;
485  }
486 }
retrieveNewFilesAcrossForks($mainline, GitRepo $repo, $branchName)
output($string, $level=INFO, $label='')
cleanup($repo, $mainline)
getBranches($source='--all')
const CHANGE_TYPE_MODIFIED
removeRemote($alias)
exec($command, array &$output=null, &$return_var=null)
compareChanges($remoteAlias, $remoteBranch, $changesType=self::CHANGE_TYPE_ALL)
defined('TESTS_BP')||define('TESTS_BP' __DIR__
Definition: _bootstrap.php:60
$source
Definition: source.php:23
validateInput(array $options, array $requiredOptions)
__construct($workTree)
saveChangedFileContent(GitRepo $repo)
$mask
Definition: bootstrap.php:36
getRepo($options, $mainline)
$fileName
Definition: translate.phtml:15
$additionsFile
const CHANGE_TYPE_ALL
const USAGE
exit
Definition: redirect.phtml:12
retrieveChangesAcrossForks($mainline, GitRepo $repo, $branchName)
const CHANGE_TYPE_ADDED
if(!validateInput($options, $requiredOptions)) $fileExtensions
const BP
Definition: autoload.php:14
generateBranchesList($outputFile, $branches, $branchName)
if(!trim($html)) $alias
Definition: details.phtml:20
$requiredOptions
addRemote($alias, $url)
filterChangedFiles(array $changes, $remoteAlias, $remoteBranch, $changesType=self::CHANGE_TYPE_ALL)
foreach($appDirs as $dir) $files
generateChangedFilesList($outputFile, $changedFiles)
getChangedFiles(array $changes, array $fileExtensions)
fetch($remoteAlias)