Perforce.php 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581
  1. <?php
  2. /*
  3. * This file is part of Composer.
  4. *
  5. * (c) Nils Adermann <naderman@naderman.de>
  6. * Jordi Boggiano <j.boggiano@seld.be>
  7. *
  8. * For the full copyright and license information, please view the LICENSE
  9. * file that was distributed with this source code.
  10. */
  11. namespace Composer\Util;
  12. use Composer\IO\IOInterface;
  13. use Symfony\Component\Process\Process;
  14. /**
  15. * @author Matt Whittom <Matt.Whittom@veteransunited.com>
  16. */
  17. class Perforce
  18. {
  19. protected $path;
  20. protected $p4Depot;
  21. protected $p4Client;
  22. protected $p4User;
  23. protected $p4Password;
  24. protected $p4Port;
  25. protected $p4Stream;
  26. protected $p4ClientSpec;
  27. protected $p4DepotType;
  28. protected $p4Branch;
  29. protected $process;
  30. protected $uniquePerforceClientName;
  31. protected $windowsFlag;
  32. protected $commandResult;
  33. protected $io;
  34. protected $filesystem;
  35. public function __construct($repoConfig, $port, $path, ProcessExecutor $process, $isWindows, IOInterface $io)
  36. {
  37. $this->windowsFlag = $isWindows;
  38. $this->p4Port = $port;
  39. $this->initializePath($path);
  40. $this->process = $process;
  41. $this->initialize($repoConfig);
  42. $this->io = $io;
  43. }
  44. public static function create($repoConfig, $port, $path, ProcessExecutor $process, IOInterface $io)
  45. {
  46. return new Perforce($repoConfig, $port, $path, $process, Platform::isWindows(), $io);
  47. }
  48. public static function checkServerExists($url, ProcessExecutor $processExecutor)
  49. {
  50. $output = null;
  51. return 0 === $processExecutor->execute('p4 -p ' . ProcessExecutor::escape($url) . ' info -s', $output);
  52. }
  53. public function initialize($repoConfig)
  54. {
  55. $this->uniquePerforceClientName = $this->generateUniquePerforceClientName();
  56. if (!$repoConfig) {
  57. return;
  58. }
  59. if (isset($repoConfig['unique_perforce_client_name'])) {
  60. $this->uniquePerforceClientName = $repoConfig['unique_perforce_client_name'];
  61. }
  62. if (isset($repoConfig['depot'])) {
  63. $this->p4Depot = $repoConfig['depot'];
  64. }
  65. if (isset($repoConfig['branch'])) {
  66. $this->p4Branch = $repoConfig['branch'];
  67. }
  68. if (isset($repoConfig['p4user'])) {
  69. $this->p4User = $repoConfig['p4user'];
  70. } else {
  71. $this->p4User = $this->getP4variable('P4USER');
  72. }
  73. if (isset($repoConfig['p4password'])) {
  74. $this->p4Password = $repoConfig['p4password'];
  75. }
  76. }
  77. public function initializeDepotAndBranch($depot, $branch)
  78. {
  79. if (isset($depot)) {
  80. $this->p4Depot = $depot;
  81. }
  82. if (isset($branch)) {
  83. $this->p4Branch = $branch;
  84. }
  85. }
  86. public function generateUniquePerforceClientName()
  87. {
  88. return gethostname() . "_" . time();
  89. }
  90. public function cleanupClientSpec()
  91. {
  92. $client = $this->getClient();
  93. $task = 'client -d ' . ProcessExecutor::escape($client);
  94. $useP4Client = false;
  95. $command = $this->generateP4Command($task, $useP4Client);
  96. $this->executeCommand($command);
  97. $clientSpec = $this->getP4ClientSpec();
  98. $fileSystem = $this->getFilesystem();
  99. $fileSystem->remove($clientSpec);
  100. }
  101. protected function executeCommand($command)
  102. {
  103. $this->commandResult = '';
  104. return $this->process->execute($command, $this->commandResult);
  105. }
  106. public function getClient()
  107. {
  108. if (!isset($this->p4Client)) {
  109. $cleanStreamName = str_replace(array('//', '/', '@'), array('', '_', ''), $this->getStream());
  110. $this->p4Client = 'composer_perforce_' . $this->uniquePerforceClientName . '_' . $cleanStreamName;
  111. }
  112. return $this->p4Client;
  113. }
  114. protected function getPath()
  115. {
  116. return $this->path;
  117. }
  118. public function initializePath($path)
  119. {
  120. $this->path = $path;
  121. $fs = $this->getFilesystem();
  122. $fs->ensureDirectoryExists($path);
  123. }
  124. protected function getPort()
  125. {
  126. return $this->p4Port;
  127. }
  128. public function setStream($stream)
  129. {
  130. $this->p4Stream = $stream;
  131. $index = strrpos($stream, '/');
  132. //Stream format is //depot/stream, while non-streaming depot is //depot
  133. if ($index > 2) {
  134. $this->p4DepotType = 'stream';
  135. }
  136. }
  137. public function isStream()
  138. {
  139. return (strcmp($this->p4DepotType, 'stream') === 0);
  140. }
  141. public function getStream()
  142. {
  143. if (!isset($this->p4Stream)) {
  144. if ($this->isStream()) {
  145. $this->p4Stream = '//' . $this->p4Depot . '/' . $this->p4Branch;
  146. } else {
  147. $this->p4Stream = '//' . $this->p4Depot;
  148. }
  149. }
  150. return $this->p4Stream;
  151. }
  152. public function getStreamWithoutLabel($stream)
  153. {
  154. $index = strpos($stream, '@');
  155. if ($index === false) {
  156. return $stream;
  157. }
  158. return substr($stream, 0, $index);
  159. }
  160. public function getP4ClientSpec()
  161. {
  162. return $this->path . '/' . $this->getClient() . '.p4.spec';
  163. }
  164. public function getUser()
  165. {
  166. return $this->p4User;
  167. }
  168. public function setUser($user)
  169. {
  170. $this->p4User = $user;
  171. }
  172. public function queryP4User()
  173. {
  174. $this->getUser();
  175. if (strlen($this->p4User) > 0) {
  176. return;
  177. }
  178. $this->p4User = $this->getP4variable('P4USER');
  179. if (strlen($this->p4User) > 0) {
  180. return;
  181. }
  182. $this->p4User = $this->io->ask('Enter P4 User:');
  183. if ($this->windowsFlag) {
  184. $command = 'p4 set P4USER=' . $this->p4User;
  185. } else {
  186. $command = 'export P4USER=' . $this->p4User;
  187. }
  188. $this->executeCommand($command);
  189. }
  190. protected function getP4variable($name)
  191. {
  192. if ($this->windowsFlag) {
  193. $command = 'p4 set';
  194. $this->executeCommand($command);
  195. $result = trim($this->commandResult);
  196. $resArray = explode(PHP_EOL, $result);
  197. foreach ($resArray as $line) {
  198. $fields = explode('=', $line);
  199. if (strcmp($name, $fields[0]) == 0) {
  200. $index = strpos($fields[1], ' ');
  201. if ($index === false) {
  202. $value = $fields[1];
  203. } else {
  204. $value = substr($fields[1], 0, $index);
  205. }
  206. $value = trim($value);
  207. return $value;
  208. }
  209. }
  210. return null;
  211. }
  212. $command = 'echo $' . $name;
  213. $this->executeCommand($command);
  214. $result = trim($this->commandResult);
  215. return $result;
  216. }
  217. public function queryP4Password()
  218. {
  219. if (isset($this->p4Password)) {
  220. return $this->p4Password;
  221. }
  222. $password = $this->getP4variable('P4PASSWD');
  223. if (strlen($password) <= 0) {
  224. $password = $this->io->askAndHideAnswer('Enter password for Perforce user ' . $this->getUser() . ': ');
  225. }
  226. $this->p4Password = $password;
  227. return $password;
  228. }
  229. public function generateP4Command($command, $useClient = true)
  230. {
  231. $p4Command = 'p4 ';
  232. $p4Command .= '-u ' . $this->getUser() . ' ';
  233. if ($useClient) {
  234. $p4Command .= '-c ' . $this->getClient() . ' ';
  235. }
  236. $p4Command = $p4Command . '-p ' . $this->getPort() . ' ' . $command;
  237. return $p4Command;
  238. }
  239. public function isLoggedIn()
  240. {
  241. $command = $this->generateP4Command('login -s', false);
  242. $exitCode = $this->executeCommand($command);
  243. if ($exitCode) {
  244. $errorOutput = $this->process->getErrorOutput();
  245. $index = strpos($errorOutput, $this->getUser());
  246. if ($index === false) {
  247. $index = strpos($errorOutput, 'p4');
  248. if ($index === false) {
  249. return false;
  250. }
  251. throw new \Exception('p4 command not found in path: ' . $errorOutput);
  252. }
  253. throw new \Exception('Invalid user name: ' . $this->getUser());
  254. }
  255. return true;
  256. }
  257. public function connectClient()
  258. {
  259. $p4CreateClientCommand = $this->generateP4Command(
  260. 'client -i < ' . str_replace(" ", "\\ ", $this->getP4ClientSpec())
  261. );
  262. $this->executeCommand($p4CreateClientCommand);
  263. }
  264. public function syncCodeBase($sourceReference)
  265. {
  266. $prevDir = getcwd();
  267. chdir($this->path);
  268. $p4SyncCommand = $this->generateP4Command('sync -f ');
  269. if (null !== $sourceReference) {
  270. $p4SyncCommand .= '@' . $sourceReference;
  271. }
  272. $this->executeCommand($p4SyncCommand);
  273. chdir($prevDir);
  274. }
  275. public function writeClientSpecToFile($spec)
  276. {
  277. fwrite($spec, 'Client: ' . $this->getClient() . PHP_EOL . PHP_EOL);
  278. fwrite($spec, 'Update: ' . date('Y/m/d H:i:s') . PHP_EOL . PHP_EOL);
  279. fwrite($spec, 'Access: ' . date('Y/m/d H:i:s') . PHP_EOL);
  280. fwrite($spec, 'Owner: ' . $this->getUser() . PHP_EOL . PHP_EOL);
  281. fwrite($spec, 'Description:' . PHP_EOL);
  282. fwrite($spec, ' Created by ' . $this->getUser() . ' from composer.' . PHP_EOL . PHP_EOL);
  283. fwrite($spec, 'Root: ' . $this->getPath() . PHP_EOL . PHP_EOL);
  284. fwrite($spec, 'Options: noallwrite noclobber nocompress unlocked modtime rmdir' . PHP_EOL . PHP_EOL);
  285. fwrite($spec, 'SubmitOptions: revertunchanged' . PHP_EOL . PHP_EOL);
  286. fwrite($spec, 'LineEnd: local' . PHP_EOL . PHP_EOL);
  287. if ($this->isStream()) {
  288. fwrite($spec, 'Stream:' . PHP_EOL);
  289. fwrite($spec, ' ' . $this->getStreamWithoutLabel($this->p4Stream) . PHP_EOL);
  290. } else {
  291. fwrite(
  292. $spec,
  293. 'View: ' . $this->getStream() . '/... //' . $this->getClient() . '/... ' . PHP_EOL
  294. );
  295. }
  296. }
  297. public function writeP4ClientSpec()
  298. {
  299. $clientSpec = $this->getP4ClientSpec();
  300. $spec = fopen($clientSpec, 'w');
  301. try {
  302. $this->writeClientSpecToFile($spec);
  303. } catch (\Exception $e) {
  304. fclose($spec);
  305. throw $e;
  306. }
  307. fclose($spec);
  308. }
  309. protected function read($pipe, $name)
  310. {
  311. if (feof($pipe)) {
  312. return;
  313. }
  314. $line = fgets($pipe);
  315. while ($line !== false) {
  316. $line = fgets($pipe);
  317. }
  318. return;
  319. }
  320. public function windowsLogin($password)
  321. {
  322. $command = $this->generateP4Command(' login -a');
  323. // TODO in v3 generate command as an array
  324. if (method_exists('Symfony\Component\Process\Process', 'fromShellCommandline')) {
  325. $process = Process::fromShellCommandline($command, null, null, $password);
  326. } else {
  327. $process = new Process($command, null, null, $password);
  328. }
  329. return $process->run();
  330. }
  331. public function p4Login()
  332. {
  333. $this->queryP4User();
  334. if (!$this->isLoggedIn()) {
  335. $password = $this->queryP4Password();
  336. if ($this->windowsFlag) {
  337. $this->windowsLogin($password);
  338. } else {
  339. $command = 'echo ' . ProcessExecutor::escape($password) . ' | ' . $this->generateP4Command(' login -a', false);
  340. $exitCode = $this->executeCommand($command);
  341. $result = trim($this->commandResult);
  342. if ($exitCode) {
  343. throw new \Exception("Error logging in:" . $this->process->getErrorOutput());
  344. }
  345. }
  346. }
  347. }
  348. public function getComposerInformation($identifier)
  349. {
  350. $composerFileContent = $this->getFileContent('composer.json', $identifier);
  351. if (!$composerFileContent) {
  352. return;
  353. }
  354. return json_decode($composerFileContent, true);
  355. }
  356. public function getFileContent($file, $identifier)
  357. {
  358. $path = $this->getFilePath($file, $identifier);
  359. $command = $this->generateP4Command(' print ' . ProcessExecutor::escape($path));
  360. $this->executeCommand($command);
  361. $result = $this->commandResult;
  362. if (!trim($result)) {
  363. return null;
  364. }
  365. return $result;
  366. }
  367. public function getFilePath($file, $identifier)
  368. {
  369. $index = strpos($identifier, '@');
  370. if ($index === false) {
  371. $path = $identifier. '/' . $file;
  372. return $path;
  373. }
  374. $path = substr($identifier, 0, $index) . '/' . $file . substr($identifier, $index);
  375. $command = $this->generateP4Command(' files ' . ProcessExecutor::escape($path), false);
  376. $this->executeCommand($command);
  377. $result = $this->commandResult;
  378. $index2 = strpos($result, 'no such file(s).');
  379. if ($index2 === false) {
  380. $index3 = strpos($result, 'change');
  381. if ($index3 !== false) {
  382. $phrase = trim(substr($result, $index3));
  383. $fields = explode(' ', $phrase);
  384. return substr($identifier, 0, $index) . '/' . $file . '@' . $fields[1];
  385. }
  386. }
  387. return null;
  388. }
  389. public function getBranches()
  390. {
  391. $possibleBranches = array();
  392. if (!$this->isStream()) {
  393. $possibleBranches[$this->p4Branch] = $this->getStream();
  394. } else {
  395. $command = $this->generateP4Command('streams '.ProcessExecutor::escape('//' . $this->p4Depot . '/...'));
  396. $this->executeCommand($command);
  397. $result = $this->commandResult;
  398. $resArray = explode(PHP_EOL, $result);
  399. foreach ($resArray as $line) {
  400. $resBits = explode(' ', $line);
  401. if (count($resBits) > 4) {
  402. $branch = preg_replace('/[^A-Za-z0-9 ]/', '', $resBits[4]);
  403. $possibleBranches[$branch] = $resBits[1];
  404. }
  405. }
  406. }
  407. $command = $this->generateP4Command('changes '. ProcessExecutor::escape($this->getStream() . '/...'), false);
  408. $this->executeCommand($command);
  409. $result = $this->commandResult;
  410. $resArray = explode(PHP_EOL, $result);
  411. $lastCommit = $resArray[0];
  412. $lastCommitArr = explode(' ', $lastCommit);
  413. $lastCommitNum = $lastCommitArr[1];
  414. $branches = array('master' => $possibleBranches[$this->p4Branch] . '@'. $lastCommitNum);
  415. return $branches;
  416. }
  417. public function getTags()
  418. {
  419. $command = $this->generateP4Command('labels');
  420. $this->executeCommand($command);
  421. $result = $this->commandResult;
  422. $resArray = explode(PHP_EOL, $result);
  423. $tags = array();
  424. foreach ($resArray as $line) {
  425. if (strpos($line, 'Label') !== false) {
  426. $fields = explode(' ', $line);
  427. $tags[$fields[1]] = $this->getStream() . '@' . $fields[1];
  428. }
  429. }
  430. return $tags;
  431. }
  432. public function checkStream()
  433. {
  434. $command = $this->generateP4Command('depots', false);
  435. $this->executeCommand($command);
  436. $result = $this->commandResult;
  437. $resArray = explode(PHP_EOL, $result);
  438. foreach ($resArray as $line) {
  439. if (strpos($line, 'Depot') !== false) {
  440. $fields = explode(' ', $line);
  441. if (strcmp($this->p4Depot, $fields[1]) === 0) {
  442. $this->p4DepotType = $fields[3];
  443. return $this->isStream();
  444. }
  445. }
  446. }
  447. return false;
  448. }
  449. /**
  450. * @param string $reference
  451. * @return mixed|null
  452. */
  453. protected function getChangeList($reference)
  454. {
  455. $index = strpos($reference, '@');
  456. if ($index === false) {
  457. return null;
  458. }
  459. $label = substr($reference, $index);
  460. $command = $this->generateP4Command(' changes -m1 ' . ProcessExecutor::escape($label));
  461. $this->executeCommand($command);
  462. $changes = $this->commandResult;
  463. if (strpos($changes, 'Change') !== 0) {
  464. return null;
  465. }
  466. $fields = explode(' ', $changes);
  467. return $fields[1];
  468. }
  469. /**
  470. * @param string $fromReference
  471. * @param string $toReference
  472. * @return mixed|null
  473. */
  474. public function getCommitLogs($fromReference, $toReference)
  475. {
  476. $fromChangeList = $this->getChangeList($fromReference);
  477. if ($fromChangeList === null) {
  478. return null;
  479. }
  480. $toChangeList = $this->getChangeList($toReference);
  481. if ($toChangeList === null) {
  482. return null;
  483. }
  484. $index = strpos($fromReference, '@');
  485. $main = substr($fromReference, 0, $index) . '/...';
  486. $command = $this->generateP4Command('filelog ' . ProcessExecutor::escape($main . '@' . $fromChangeList. ',' . $toChangeList));
  487. $this->executeCommand($command);
  488. return $this->commandResult;
  489. }
  490. public function getFilesystem()
  491. {
  492. if (empty($this->filesystem)) {
  493. $this->filesystem = new Filesystem($this->process);
  494. }
  495. return $this->filesystem;
  496. }
  497. public function setFilesystem(Filesystem $fs)
  498. {
  499. $this->filesystem = $fs;
  500. }
  501. }