Files
faveo/vendor/gitonomy/gitlib/src/Gitonomy/Git/Repository.php
RafficMohammed da241bacb6 updated-packages
2023-01-08 00:13:22 +05:30

641 lines
17 KiB
PHP

<?php
/**
* This file is part of Gitonomy.
*
* (c) Alexandre Salomé <alexandre.salome@gmail.com>
* (c) Julien DIDIER <genzo.wm@gmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Gitonomy\Git;
use Gitonomy\Git\Diff\Diff;
use Gitonomy\Git\Exception\InvalidArgumentException;
use Gitonomy\Git\Exception\ProcessException;
use Gitonomy\Git\Exception\RuntimeException;
use Psr\Log\LoggerInterface;
use Symfony\Component\Process\Process;
/**
* Git repository object.
*
* Main entry point for browsing a Git repository.
*
* @author Alexandre Salomé <alexandre.salome@gmail.com>
*/
class Repository
{
const DEFAULT_DESCRIPTION = "Unnamed repository; edit this file 'description' to name the repository.\n";
/**
* Directory containing git files.
*
* @var string
*/
protected $gitDir;
/**
* Working directory.
*
* @var string
*/
protected $workingDir;
/**
* Cache containing all objects of the repository.
*
* Associative array, indexed by object hash
*
* @var array
*/
protected $objects;
/**
* Reference bag associated to this repository.
*
* @var ReferenceBag
*/
protected $referenceBag;
/**
* Logger (can be null).
*
* @var LoggerInterface
*/
protected $logger;
/**
* Path to git command.
*/
protected $command;
/**
* Debug flag, indicating if errors should be thrown.
*
* @var bool
*/
protected $debug;
/**
* Environment variables that should be set for every running process.
*
* @var array
*/
protected $environmentVariables;
/**
* @var bool
*/
protected $inheritEnvironmentVariables;
/**
* Timeout that should be set for every running process.
*
* @var int
*/
protected $processTimeout;
/**
* Constructs a new repository.
*
* Available options are:
*
* * working_dir : specify where working copy is located (option --work-tree)
*
* * debug : (default: true) enable/disable minimize errors and reduce log level
*
* * logger : a logger to use for logging actions (Psr\Log\LoggerInterface)
*
* * environment_variables : define environment variables for every ran process
*
* @param string $dir path to git repository
* @param array $options array of options values
*
* @throws InvalidArgumentException The folder does not exists
*/
public function __construct($dir, $options = [])
{
$options = array_merge([
'working_dir' => null,
'debug' => true,
'logger' => null,
'command' => 'git',
'environment_variables' => [],
'inherit_environment_variables' => false,
'process_timeout' => 3600,
], $options);
if (null !== $options['logger'] && !$options['logger'] instanceof LoggerInterface) {
throw new InvalidArgumentException(sprintf('Argument "logger" passed to Repository should be a Psr\Log\LoggerInterface. A %s was provided', is_object($options['logger']) ? get_class($options['logger']) : gettype($options['logger'])));
}
$this->logger = $options['logger'];
$this->initDir($dir, $options['working_dir']);
$this->objects = [];
$this->command = $options['command'];
$this->debug = (bool) $options['debug'];
$this->processTimeout = $options['process_timeout'];
if (defined('PHP_WINDOWS_VERSION_BUILD') && isset($_SERVER['PATH']) && !isset($options['environment_variables']['PATH'])) {
$options['environment_variables']['PATH'] = $_SERVER['PATH'];
}
$this->environmentVariables = $options['environment_variables'];
$this->inheritEnvironmentVariables = $options['inherit_environment_variables'];
if (true === $this->debug && null !== $this->logger) {
$this->logger->debug(sprintf('Repository created (git dir: "%s", working dir: "%s")', $this->gitDir, $this->workingDir ?: 'none'));
}
}
/**
* Initializes directory attributes on repository:.
*
* @param string $gitDir directory of a working copy with files checked out
* @param string $workingDir directory containing git files (objects, config...)
*/
private function initDir($gitDir, $workingDir = null)
{
$realGitDir = realpath($gitDir);
if (false === $realGitDir) {
throw new InvalidArgumentException(sprintf('Directory "%s" does not exist or is not a directory', $gitDir));
} elseif (!is_dir($realGitDir)) {
throw new InvalidArgumentException(sprintf('Directory "%s" does not exist or is not a directory', $realGitDir));
} elseif (null === $workingDir && is_dir($realGitDir.'/.git')) {
$workingDir = $realGitDir;
$realGitDir = $realGitDir.'/.git';
}
$this->gitDir = $realGitDir;
$this->workingDir = $workingDir;
}
/**
* Tests if repository is a bare repository.
*
* @return bool
*/
public function isBare()
{
return null === $this->workingDir;
}
/**
* Returns the HEAD resolved as a commit.
*
* @return Commit|null returns a Commit or ``null`` if repository is empty
*/
public function getHeadCommit()
{
$head = $this->getHead();
if ($head instanceof Reference) {
return $head->getCommit();
}
return $head;
}
/**
* @throws RuntimeException Unable to find file HEAD (debug-mode only)
*
* @return Reference|Commit|null current HEAD object or null if error occurs
*/
public function getHead()
{
$file = $this->gitDir.'/HEAD';
if (!file_exists($file)) {
$message = sprintf('Unable to find HEAD file ("%s")', $file);
if (null !== $this->logger) {
$this->logger->error($message);
}
if (true === $this->debug) {
throw new RuntimeException($message);
}
}
$content = trim(file_get_contents($file));
if (null !== $this->logger) {
$this->logger->debug('HEAD file read: '.$content);
}
if (preg_match('/^ref: (.+)$/', $content, $vars)) {
return $this->getReferences()->get($vars[1]);
} elseif (preg_match('/^[0-9a-f]{40}$/', $content)) {
return $this->getCommit($content);
}
$message = sprintf('Unexpected HEAD file content (file: %s). Content of file: %s', $file, $content);
if (null !== $this->logger) {
$this->logger->error($message);
}
if (true === $this->debug) {
throw new RuntimeException($message);
}
}
/**
* @return bool
*/
public function isHeadDetached()
{
return !$this->isHeadAttached();
}
/**
* @return bool
*/
public function isHeadAttached()
{
return $this->getHead() instanceof Reference;
}
/**
* Returns the path to the git repository.
*
* @return string A directory path
*/
public function getPath()
{
return $this->workingDir === null ? $this->gitDir : $this->workingDir;
}
/**
* Returns the directory containing git files (git-dir).
*
* @return string
*/
public function getGitDir()
{
return $this->gitDir;
}
/**
* Returns the work-tree directory. This may be null if repository is
* bare.
*
* @return string path to repository or null if repository is bare
*/
public function getWorkingDir()
{
return $this->workingDir;
}
/**
* Instanciates a revision.
*
* @param string $name Name of the revision
*
* @return Revision
*/
public function getRevision($name)
{
return new Revision($this, $name);
}
/**
* @return WorkingCopy
*/
public function getWorkingCopy()
{
return new WorkingCopy($this);
}
/**
* Returns the reference list associated to the repository.
*
* @param bool $reload Reload references from the filesystem
*
* @return ReferenceBag
*/
public function getReferences($reload = false)
{
if (null === $this->referenceBag || $reload) {
$this->referenceBag = new ReferenceBag($this);
}
return $this->referenceBag;
}
/**
* Instanciates a commit object or fetches one from the cache.
*
* @param string $hash A commit hash, with a length of 40
*
* @return Commit
*/
public function getCommit($hash)
{
if (!isset($this->objects[$hash])) {
$this->objects[$hash] = new Commit($this, $hash);
}
return $this->objects[$hash];
}
/**
* Instanciates a tree object or fetches one from the cache.
*
* @param string $hash A tree hash, with a length of 40
*
* @return Tree
*/
public function getTree($hash)
{
if (!isset($this->objects[$hash])) {
$this->objects[$hash] = new Tree($this, $hash);
}
return $this->objects[$hash];
}
/**
* Instanciates a blob object or fetches one from the cache.
*
* @param string $hash A blob hash, with a length of 40
*
* @return Blob
*/
public function getBlob($hash)
{
if (!isset($this->objects[$hash])) {
$this->objects[$hash] = new Blob($this, $hash);
}
return $this->objects[$hash];
}
/**
* @return Blame
*/
public function getBlame($revision, $file, $lineRange = null)
{
if (is_string($revision)) {
$revision = $this->getRevision($revision);
}
return new Blame($this, $revision, $file, $lineRange);
}
/**
* Returns log for a given set of revisions and paths.
*
* All those values can be null, meaning everything.
*
* @param array $revisions An array of revisions to show logs from. Can be
* any text value type
* @param array $paths Restrict log to modifications occurring on given
* paths.
* @param int $offset Start from a given offset in results.
* @param int $limit Limit number of total results.
*
* @return Log
*/
public function getLog($revisions = null, $paths = null, $offset = null, $limit = null)
{
return new Log($this, $revisions, $paths, $offset, $limit);
}
/**
* @return Diff
*/
public function getDiff($revisions)
{
if (null !== $revisions && !$revisions instanceof RevisionList) {
$revisions = new RevisionList($this, $revisions);
}
$args = array_merge(['-r', '-p', '-m', '-M', '--no-commit-id', '--full-index'], $revisions->getAsTextArray());
$diff = Diff::parse($this->run('diff', $args));
$diff->setRepository($this);
return $diff;
}
/**
* Returns the size of repository, in kilobytes.
*
* @return int A sum, in kilobytes
*/
public function getSize()
{
$totalBytes = 0;
$path = realpath($this->gitDir);
if ($path && file_exists($path)) {
foreach (new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($path, \FilesystemIterator::SKIP_DOTS)) as $object) {
$totalBytes += $object->getSize();
}
}
return (int) ($totalBytes / 1000 + 0.5);
}
/**
* Executes a shell command on the repository, using PHP pipes.
*
* @param string $command The command to execute
*/
public function shell($command, array $env = [])
{
$argument = sprintf('%s \'%s\'', $command, $this->gitDir);
$prefix = '';
foreach ($env as $name => $value) {
$prefix .= sprintf('export %s=%s;', escapeshellarg($name), escapeshellarg($value));
}
proc_open($prefix.'git shell -c '.escapeshellarg($argument), [STDIN, STDOUT, STDERR], $pipes);
}
/**
* Returns the hooks object.
*
* @return Hooks
*/
public function getHooks()
{
return new Hooks($this);
}
/**
* Returns description of repository from description file in git directory.
*
* @return string The description
*/
public function getDescription()
{
$file = $this->gitDir.'/description';
$exists = is_file($file);
if (null !== $this->logger && true === $this->debug) {
if (false === $exists) {
$this->logger->debug(sprintf('no description file in repository ("%s")', $file));
} else {
$this->logger->debug(sprintf('reading description file in repository ("%s")', $file));
}
}
if (false === $exists) {
return static::DEFAULT_DESCRIPTION;
}
return file_get_contents($this->gitDir.'/description');
}
/**
* Tests if repository has a custom set description.
*
* @return bool
*/
public function hasDescription()
{
return static::DEFAULT_DESCRIPTION !== $this->getDescription();
}
/**
* Changes the repository description (file description in git-directory).
*
* @return Repository the current repository
*/
public function setDescription($description)
{
$file = $this->gitDir.'/description';
if (null !== $this->logger && true === $this->debug) {
$this->logger->debug(sprintf('change description file content to "%s" (file: %s)', $description, $file));
}
file_put_contents($file, $description);
return $this;
}
/**
* This command is a facility command. You can run any command
* directly on git repository.
*
* @param string $command Git command to run (checkout, branch, tag)
* @param array $args Arguments of git command
*
* @throws RuntimeException Error while executing git command (debug-mode only)
*
* @return string Output of a successful process or null if execution failed and debug-mode is disabled.
*/
public function run($command, $args = [])
{
$process = $this->getProcess($command, $args);
if ($this->logger) {
$this->logger->info(sprintf('run command: %s "%s" ', $command, implode(' ', $args)));
$before = microtime(true);
}
$process->run();
$output = $process->getOutput();
if ($this->logger && $this->debug) {
$duration = microtime(true) - $before;
$this->logger->debug(sprintf('last command (%s) duration: %sms', $command, sprintf('%.2f', $duration * 1000)));
$this->logger->debug(sprintf('last command (%s) return code: %s', $command, $process->getExitCode()));
$this->logger->debug(sprintf('last command (%s) output: %s', $command, $output));
}
if (!$process->isSuccessful()) {
$error = sprintf("error while running %s\n output: \"%s\"", $command, $process->getErrorOutput());
if ($this->logger) {
$this->logger->error($error);
}
if ($this->debug) {
throw new ProcessException($process);
}
return;
}
return $output;
}
/**
* Set repository logger.
*
* @param LoggerInterface $logger A logger
*
* @return Repository The current repository
*/
public function setLogger(LoggerInterface $logger)
{
$this->logger = $logger;
return $this;
}
/**
* Returns repository logger.
*
* @return LoggerInterface the logger or null
*/
public function getLogger()
{
return $this->logger;
}
/**
* Clones the current repository to a new directory and return instance of new repository.
*
* @param string $path path to the new repository in which current repository will be cloned
* @param bool $bare flag indicating if repository is bare or has a working-copy
*
* @return Repository the newly created repository
*/
public function cloneTo($path, $bare = true, array $options = [])
{
return Admin::cloneTo($path, $this->gitDir, $bare, $options);
}
/**
* This internal method is used to create a process object.
*
* Made private to be sure that process creation is handled through the run method.
* run method ensures logging and debug.
*
* @see self::run
*/
private function getProcess($command, $args = [])
{
$base = [$this->command, '--git-dir', $this->gitDir];
if ($this->workingDir) {
$base = array_merge($base, ['--work-tree', $this->workingDir]);
}
$base[] = $command;
$process = new Process(array_merge($base, $args));
if ($this->inheritEnvironmentVariables) {
$process->setEnv(array_replace($_SERVER, $this->environmentVariables));
} else {
$process->setEnv($this->environmentVariables);
}
$process->setTimeout($this->processTimeout);
$process->setIdleTimeout($this->processTimeout);
return $process;
}
}