$output = $this->run(['ls-tree', '-lz', $hash, '--', $path], $repository);
$tree = $this->buildTreeFromOutput($repository, $hash, $output, true);
$tree->setName(rtrim($path, '/'));
return $tree;
}
public function getCommit(Repository $repository, ?string $hash = 'HEAD'): Commit
{
$output = $this->run(['show', '--ignore-blank-lines', '-w', '-b', '--cc', self::DEFAULT_COMMIT_FORMAT, $hash], $repository);
$commits = $this->parseCommitDataXml($repository, $output);
$commit = reset($commits);
$rawDiffBlock = substr($output, strpos($output, '</item>') + 7);
$commit->setRawDiffs($rawDiffBlock);
$fileDiffs = (new Parse())->fromRawBlock($rawDiffBlock);
$commit->setDiffs($fileDiffs);
return $commit;
}
public function getCommits(Repository $repository, ?string $hash = 'HEAD', int $page = 1, int $perPage = 10): array
{
$output = $this->run([
'log',
'--skip',
($page - 1) * $perPage,
'--max-count',
$page * $perPage,
self::DEFAULT_COMMIT_FORMAT,
$hash,
], $repository);
return $this->parseCommitsDataXml($repository, $output);
}
public function getCommitsFromPath(Repository $repository, string $path, ?string $hash = 'HEAD', int $page = 1, int $perPage = 10): array
{
$output = $this->run([
'log',
'--skip',
($page - 1) * $perPage,
'--max-count',
$page * $perPage,
self::DEFAULT_COMMIT_FORMAT,
$hash,
'--',
$path,
], $repository);
return $this->parseCommitsDataXml($repository, $output);
}
public function getSpecificCommits(Repository $repository, array $hashes): array
{
$output = $this->run([...['show', '-s', self::DEFAULT_COMMIT_FORMAT], ...$hashes], $repository);
return $this->parseCommitsDataXml($repository, $output);
}
public function getBlame(Repository $repository, string $hash, string $path): Blame
{
$output = $this->run(['blame', '--root', '-ls', $hash, '--', $path], $repository);
$blameLines = explode(PHP_EOL, $output);
$annotatedLines = [];
$commits = [];
foreach ($blameLines as $blameLine) {
if (empty($blameLine)) {
continue;
}
$blameParts = [];
preg_match('/([a-zA-Z0-9^]{40})\s+.*?([0-9]+)\)\s+(.+)?/', $blameLine, $blameParts);
$commits[] = $blameParts[1];
$annotatedLines[] = [
'commit' => $blameParts[1],
'line' => ltrim(str_replace($blameParts[1], '', $blameParts[0])),
];
}
$blame = new Blame($hash, $path);
$commits = $this->getSpecificCommits($repository, array_unique($commits));
foreach ($annotatedLines as $annotatedLine) {
$commit = $commits[$annotatedLine['commit']];
$blame->addAnnotatedLine(new AnnotatedLine($commit, $annotatedLine['line']));
}
return $blame;
}
public function getBlob(Repository $repository, string $hash, string $path): Blob
{
$commits = $this->getCommitsFromPath($repository, $path, $hash, 1, 1);
$commit = reset($commits);
$blobOutput = $this->run(['show', sprintf('%s:%s', $hash, $path)], $repository);
$blob = new Blob($repository, $commit->getHash(), $commit->getShortHash());
$blob->setName($path);
$blob->setContents($blobOutput);
return $blob;
}
public function searchCommits(Repository $repository, Criteria $criteria, ?string $hash = 'HEAD'): array
{
$command = ['log', self::DEFAULT_COMMIT_FORMAT];
if ($criteria->getFrom()) {
$command[] = '--after';
$command[] = $criteria->getFrom()->format(DateTime::ISO8601);
}
if ($criteria->getTo()) {
$command[] = '--before';
$command[] = $criteria->getTo()->format(DateTime::ISO8601);
}
if ($criteria->getAuthor()) {
$command[] = '--author';
$command[] = $criteria->getAuthor();
}
if ($criteria->getMessage()) {
$command[] = '--grep';
$command[] = $criteria->getMessage();
}
$command[] = $hash;
$output = $this->run($command, $repository);
return $this->parseCommitsDataXml($repository, $output);
}
public function archive(Repository $repository, string $format, string $hash, string $path = '.'): string
{
$destination = sprintf('%s/%s.%s', sys_get_temp_dir(), $hash, $format);
$this->run(['archive', '--output', $destination, $hash, '--', $path], $repository);
return $destination;
}
protected function run(array $command, Repository $repository = null): string
{
array_unshift($command, $this->path);
$process = new Process($command);
$process->setTimeout(self::DEFAULT_TIMEOUT);
if ($repository) {
$process->setWorkingDirectory($repository->getPath());
}
try {
$process->mustRun();
} catch (ProcessFailedException $exception) {
throw new CommandException($exception->getProcess()->getErrorOutput());
}
return $process->getOutput();
}
protected function buildTreeFromOutput(Repository $repository, string $hash, string $output, bool $fetchCommitInfo = false): Tree
{
$lines = explode("\0", $output);
$root = new Tree($repository, $hash);
foreach ($lines as $line) {
if (empty($line)) {
continue;
}
$file = preg_split('/[\s]+/', $line, 5);