I'm trying to attempt using a webhook script so that I can just commit locally and have the script triggered server-side and pull in any change.
Now if I login to the server via SSH and run php webhook.php the cript is triggered successfully and the files are updated. So I know the file does work. But if I make edits to the files, commit and push to master, I'm not seeing the files updated.
The log file produced by the script suggests everything is fine, but clearly it's not.
My file structure is like this:
var
www
my-project
- webhook.php
repo-folder
So the file should be pulling the files into repo-folder and the webhook.php is set as the webhook via the bitbucket control panel. If I view the log within bitbucket, it's showing a successful request every time I push a commit.
The script:
<?php
date_default_timezone_set('Europe/London');
class Deploy {
/**
* A callback function to call after the deploy has finished.
*
* #var callback
*/
public $post_deploy;
/**
* The name of the file that will be used for logging deployments. Set to
* FALSE to disable logging.
*
* #var string
*/
private $_log = 'deployments.log';
/**
* The timestamp format used for logging.
*
* #link http://www.php.net/manual/en/function.date.php
* #var string
*/
private $_date_format = 'Y-m-d H:i:sP';
/**
* The name of the branch to pull from.
*
* #var string
*/
private $_branch = 'master';
/**
* The name of the remote to pull from.
*
* #var string
*/
private $_remote = 'origin';
/**
* The directory where your website and git repository are located, can be
* a relative or absolute path
*
* #var string
*/
private $_directory;
/**
* Sets up defaults.
*
* #param string $directory Directory where your website is located
* #param array $data Information about the deployment
*/
public function __construct($directory, $options = array())
{
// Determine the directory path
$this->_directory = realpath($directory).DIRECTORY_SEPARATOR;
$available_options = array('log', 'date_format', 'branch', 'remote');
foreach ($options as $option => $value)
{
if (in_array($option, $available_options))
{
$this->{'_'.$option} = $value;
}
}
$this->log('Attempting deployment...');
}
/**
* Writes a message to the log file.
*
* #param string $message The message to write
* #param string $type The type of log message (e.g. INFO, DEBUG, ERROR, etc.)
*/
public function log($message, $type = 'INFO')
{
if ($this->_log)
{
// Set the name of the log file
$filename = $this->_log;
if ( ! file_exists($filename))
{
// Create the log file
file_put_contents($filename, '');
// Allow anyone to write to log files
chmod($filename, 0666);
}
// Write the message into the log file
// Format: time --- type: message
file_put_contents($filename, date($this->_date_format).' --- '.$type.': '.$message.PHP_EOL, FILE_APPEND);
}
}
/**
* Executes the necessary commands to deploy the website.
*/
public function execute()
{
try
{
// Make sure we're in the right directory
chdir($this->_directory);
$this->log('Changing working directory... ');
// Discard any changes to tracked files since our last deploy
exec('git reset --hard HEAD', $output);
$this->log('Reseting repository... '.implode(' ', $output));
// Update the local repository
exec('git pull '.$this->_remote.' '.$this->_branch, $output);
$this->log('Pulling in changes... '.implode(' ', $output));
// Secure the .git directory
exec('chmod -R og-rx .git');
$this->log('Securing .git directory... ');
if (is_callable($this->post_deploy))
{
call_user_func($this->post_deploy, $this->_data);
}
$this->log('Deployment successful.');
}
catch (Exception $e)
{
$this->log($e, 'ERROR');
}
}
}
// This is just an example
$deploy = new Deploy('/var/www/site-name/repo-name');
$deploy->execute();
?>
i was researching this more and the best way you can do is like i mention find out what is the user being used by apache server by building a php script and run the shell_exec('whoami'); and run on your browser to see that user it is.
Then on your document root for your website mine per example is /var/www you would need to create shh keys for this directory when you do that also add a config file with the host bitbucket and reference to the key you created.
Add the key to bitbucket
then you would need to add permission for apache to run the git command run the command visudo and add:
yourapacheuser ALL=(yourapacheuser) NOPASSWD: /usr/bin/ #the /usr/bin in my case is where i have installed git so you need to see what is the directory path where you install git.
After that you should be able to run your script without problems, in case it complains about a requiretty message then on visudo again add: Defaults:yourapacheuser !requiretty
hope it helps
did you configure SSH keys for the server you are using in bitbucket and add the webhook with the url of your script?
As user1361389 wrote you need to know what users are running the different processes. This is how I did on an Amazon Ubuntu instance.
I have a php file that calls a bash script:
shell_exec("sudo -u ubuntu /home/ubuntu/gitpull.sh");
Create SSH keys for user ubuntu and uploaded the public key to bitbucket.
Also, make sure that the php files on your server are owned by the correct user. In my case ubuntu.
You then need to impersonate ubuntu when calling the php file to deploy code. Add this line in the sudoer file (>sudo visudo )
www-data ALL=(ubuntu) NOPASSWD: /path/to/gitpull.sh
Then in bitbucket add the URL to your hook.
Related
I am using laravel Artisan Commands to download a file from public folder .
Actually i want to download a zip file which i have successfully made in public folder.
I am able to download the file when I call the methods on button click.
But when I execute the command in console it does not work and also not throw any error.
public function downloadExportRawData() {
$a=public_path().'/temp/rawexport/rawexportproduct.zip';
$path='/temp/rawexport/rawexportproduct.zip';
if(file_exists('temp/rawexport/rawexportproduct.zip')){
return Response::download($a);
}else{
return "hello";
}
}
Tried this also...
public static function downloadZip($rand_folder, $zipname, $fileName) {
header('Content-Type: application/force-download');
header('Content-Disposition: attachment; filename="' . $fileName . '.zip"');
header('Content-Length: ' . filesize($zipname));
$handle = fopen($zipname, 'rb');
//exec("rm -r ".$temp);
while (!feof($handle)) {
echo fread($handle, 1048576);
ob_flush();
flush();
}
fclose($handle);
// exec("rm -r " . $rand_folder);
}
and the command is
php artisan product:export --from=http://localhost/valuable-app/dev-master/rest_app/public/ --productid=5b273b0e4bb5995b108b4576 --tokenexport=354c3ddb2bd45aa6e5c4f749749b0b58cbacef48
The main problem is that it is going through the function in controller but not downloading it.
And I tried to echo the response it prints code in zip form means a encoded form which have something related to my zip file means its printing the zip not downloading it.
I am using Artisan first time. Any idea how to download that file using using user-defined Artisan command.
I am trying this from last two weeks but not able to do anything.
How about this:
First identify the file which you want to download and then call
string shell_exec ( string $cmd )
you can use wget to get the file.
for example, in your command
$fileName = $this->argument('filename');
$file = url('storage/public/' . $fileName);
shell_exec("wget $file");
$this->info('downloading');
if you'r using the console, the http download will not work (you are not using a browser).
Instead you can move the file to your wished destination or something like that, but not download it via console
console will not handle http response as download (will not save it)
I think you should consider to add second argument in your console:
--destination=/home/user/downloads/filetosaveas.extension and in your code use curl to download
or more simple use core curl:
curl http://example.com --output my.file --silent
It would be nice if you actually went through the nicely done Writing Commands guide at the Laravel Docs site, here.
In a nutshell, there are mainly two ways to register artisan command. Be it that one is an alternative to the other.
(Like routes) specify your instructions within a closure.
(Like controllers) create a command class and specify your instructions.
The closure way
Inside my routes/console.php, I specify something like below:
Artisan::command('download:file', function () {
// your code goes here.
})->describe('Download a file.');
Now execute a help routine for your newly created command.
-> php artisan help download:file
Description:
Donwload a file.
Usage:
donwload:file
//...
The command way
Generate the command class using php artisan make:command EmailFileCommand. This command will create app/Console/Commands/EmailFileCommand.php. Update the file to specify what we want it to do.
// ...
class EmailFileCommand extends Command
{
/**
* The name and signature of the console command.
*
* #var string
*/
protected $signature = 'email:file';
/**
* The console command description.
*
* #var string
*/
protected $description = 'Email a file.';
/**
* Create a new command instance.
*
* #return void
*/
public function __construct()
{
parent::__construct();
}
/**
* Execute the console command.
*
* #return mixed
*/
public function handle()
{
// your code goes here.
}
}
Trigger commands via HTTP
Say you want to call the commands via an http request. In my routes/web.php file,
//...
use Artisan;
//...
Route::get('/download', function () {
Artisan::call('download:file');
});
Route::get('/email', function () {
Artisan::call('email:file');
});
//...
Now execute your http requests /download and /email.
To download a file, you can use Laravel's Filesystem.
//...
use Storage;
//...
Storage::download('file.jpg')
This post has been extracted from my blog post here.
One can use the shell_exec() command to run a terminal command through PHP function or script. It will execute the command without opening the terminal window. the behaviour of command will be little bit different from the original command.
I'm having problems with logging some basic info line from Console. Locally, this works fine, everything gets logged (errors, info messages) from web and from console. But on server, error logging works fine, but when i call logger myself and try to log some info message it does not work.
I'm using Laravel 5.4 version.
This is some App\Console\Commands\DummyCommand class for test purposes.
namespace App\Console\Commands;
use Illuminate\Console\Command;
use Illuminate\Log\Writer as Log;
class DummyCommand extends Command
{
/**
* Create a new console command instance.
*
* #param \Illuminate\Log\Writer $log
* #return void
*/
public function __construct(Log $log)
{
parent::__construct();
$this->log = $log;
}
/**
* Execute the console command.
*
* #return mixed
*/
public function handle()
{
$message = "Some dummy message";
$this->info($message); // gets printed on console both locally and on server
$this->log->info($message);
$ret = $this->log->getMonolog()->addInfo($message);
dump($ret); // locally = true, server = false
if ($ret === false) {
throw new \Exception('Error while logging!'); // this log works fine both locally and on server
}
}
}
I run the following on the server:
php artisan dummy // prints "Some dummy message" and loggs error message "Error while logging!"
php artisan schedule:run // same thing as above
Any idea why is this not working ?
Permissions on storage folder is fine for all folders and files. Error messages (exceptions) gets logged but info messages does not.
Edited:
This is working, I need to add following line before calling $this->log->info(); but there is no way i would use on all Commands and every time need to set path to log file. (source: https://laracasts.com/discuss/channels/general-discussion/logging-in-l5/replies/11771)
$this->log->useDailyFiles(storage_path().'/logs/laravel.log');
Figured it out, I added multiple message type:
$this->log->getMonolog()->addAlert($message);
$this->log->getMonolog()->addCritical($message);
$this->log->getMonolog()->addDebug($message);
$this->log->getMonolog()->addError($message);
$this->log->getMonolog()->addWarning($message);
And it logs all messages with error prefix (everything except info). So I looked in .env file, and there was next line about log level:
APP_LOG_LEVEL=notice
Changed that to "debug" and now it's working fine.
I'm trying to find out how to change the default storage location (including it's subfolders) on a Lumen project. For several reasons, given the current configuration of the production web server, Lumen throws a permission denied exception when trying to write logs or compile Blade views.
The only alternative, without involving the sysadmin, is to move the storage folder to a tmp folder on the webserver.
On laravel there seems to be a method called "useStoragePath", but it doesn't seem to be available on Lumen (5.2.x).
The default paths seem to be "hardcoded", I found this:
Project\vendor\laravel\lumen-framework\src\Application.php
/**
* Get the storage path for the application.
*
* #param string|null $path
* #return string
*/
public function storagePath($path = null)
{
return $this->basePath().'/storage'.($path ? '/'.$path : $path);
}
And for the logs (same file):
/**
* Get the Monolog handler for the application.
*
* #return \Monolog\Handler\AbstractHandler
*/
protected function getMonologHandler()
{
return (new StreamHandler(storage_path('logs/lumen.log'), Logger::DEBUG))
->setFormatter(new LineFormatter(null, null, true, true));
}
Bottom line: Is there any clean way of overriding the default storage path keeping in mind this restrictions?:
It should not involve the sysadmin (sym links, changing permissions, etc.)
Not tampering with the vendor folder.
On Line 286 of vendor/laravel/lumen-framework/src/helpers.php:
if (! function_exists('storage_path')) {
/**
* Get the path to the storage folder.
*
* #param string $path
* #return string
*/
function storage_path($path = '')
{
return app()->storagePath($path);
}
}
The key here is this line:
if (! function_exists('storage_path'))
That means if a function named storage_path hasn't already been defined, then Lumen will use its own implementation.
All you have to do is simply write your own function that returns your own custom path.
Because Lumen has far fewer rules than Laravel, how you do this is entirely up to you. That said, I would suggest doing it the following way:
Place a file called helpers.php under your app directory
Add any and all custom helper functions into this file including your own storage_path implementation
Make sure this file is loaded before Lumen itself. In order to do that, you need to place your require statement before composer's autoloader. This can be done at the very first line under bootstrap/app.php:
require_once __DIR__ . '/../app/helpers.php';
require_once __DIR__ . '/../vendor/autoload.php';
try {
(new Dotenv\Dotenv(__DIR__ . '/../'))->load();
} catch (Dotenv\Exception\InvalidPathException $e) {
//
}
....
I have been trying to get code sniffer to act as a svn pre-commit hook following the guide from pear. However while i am 100% certain my code should be invalid i get no errors and the project commits without problem.
Is there something else that needs to be done besides following the guide given by pear?
Link to pear guide on code sniffer as pre-commit hook
My phpcs-svn-pre-commit file :
#!C:\wamp\bin\php\php5.4.3\php.exe
<?php
/**
* A commit hook for SVN.
*
* PHP version 5
*
* #category PHP
* #package PHP_CodeSniffer
* #author Jack Bates <ms419#freezone.co.uk>
* #author Greg Sherwood <gsherwood#squiz.net>
* #copyright 2006-2011 Squiz Pty Ltd (ABN 77 084 670 600)
* #license http://matrix.squiz.net/developer/tools/php_cs/licence BSD Licence
* #link http://pear.php.net/package/PHP_CodeSniffer
*/
if (is_file(dirname(__FILE__).'/../CodeSniffer/CLI.php') === true) {
include_once dirname(__FILE__).'/../CodeSniffer/CLI.php';
} else {
include_once 'PHP/CodeSniffer/CLI.php';
}
define('PHP_CODESNIFFER_SVNLOOK', 'C:\Program Files (x86)\VisualSVN Server\bin\svnlook');
/**
* A class to process command line options.
*
* #category PHP
* #package PHP_CodeSniffer
* #author Jack Bates <ms419#freezone.co.uk>
* #author Greg Sherwood <gsherwood#squiz.net>
* #copyright 2006-2011 Squiz Pty Ltd (ABN 77 084 670 600)
* #license http://matrix.squiz.net/developer/tools/php_cs/licence BSD Licence
* #version Release: 1.3.6
* #link http://pear.php.net/package/PHP_CodeSniffer
*/
class PHP_CodeSniffer_SVN_Hook extends PHP_CodeSniffer_CLI
{
/**
* Get a list of default values for all possible command line arguments.
*
* #return array
*/
public function getDefaults()
{
$defaults = parent::getDefaults();
$defaults['svnArgs'] = array();
return $defaults;
}//end getDefaults()
/**
* Processes an unknown command line argument.
*
* All unknown args are sent to SVN commands.
*
* #param string $arg The command line argument.
* #param int $pos The position of the argument on the command line.
* #param array $values An array of values determined from CLI args.
*
* #return array The updated CLI values.
* #see getCommandLineValues()
*/
public function processUnknownArgument($arg, $pos, $values)
{
$values['svnArgs'][] = escapeshellarg($arg);
return $values;
}//end processUnknownArgument()
/**
* Runs PHP_CodeSniffer over files are directories.
*
* #param array $values An array of values determined from CLI args.
*
* #return int The number of error and warning messages shown.
* #see getCommandLineValues()
*/
public function process($values=array())
{
if (empty($values) === true) {
$values = parent::getCommandLineValues();
}
// Get list of files in this transaction.
$command = PHP_CODESNIFFER_SVNLOOK.' changed '.implode(' ', $values['svnArgs']);
$handle = popen($command, 'r');
if ($handle === false) {
echo 'ERROR: Could not execute "'.$command.'"'.PHP_EOL.PHP_EOL;
exit(2);
}
$contents = stream_get_contents($handle);
fclose($handle);
// Do not check deleted paths.
$contents = preg_replace('/^D.*/m', null, $contents);
// Drop the four characters representing the action which precede the path on
// each line.
$contents = preg_replace('/^.{4}/m', null, $contents);
$values['standard'] = $this->validateStandard($values['standard']);
if (PHP_CodeSniffer::isInstalledStandard($values['standard']) === false) {
// They didn't select a valid coding standard, so help them
// out by letting them know which standards are installed.
echo 'ERROR: the "'.$values['standard'].'" coding standard is not installed. ';
$this->printInstalledStandards();
exit(2);
}
$phpcs = new PHP_CodeSniffer(
$values['verbosity'],
$values['tabWidth'],
$values['encoding']
);
// Set file extensions if they were specified. Otherwise,
// let PHP_CodeSniffer decide on the defaults.
if (empty($values['extensions']) === false) {
$phpcs->setAllowedFileExtensions($values['extensions']);
}
// Set ignore patterns if they were specified.
if (empty($values['ignored']) === false) {
$phpcs->setIgnorePatterns($values['ignored']);
}
// Set some convenience member vars.
if ($values['errorSeverity'] === null) {
$this->errorSeverity = PHPCS_DEFAULT_ERROR_SEV;
} else {
$this->errorSeverity = $values['errorSeverity'];
}
if ($values['warningSeverity'] === null) {
$this->warningSeverity = PHPCS_DEFAULT_WARN_SEV;
} else {
$this->warningSeverity = $values['warningSeverity'];
}
// Initialize PHP_CodeSniffer listeners but don't process any files.
$phpcs->setCli($this);
$phpcs->process(array(), $values['standard'], $values['sniffs']);
// Need double quotes around the following regex beause the vertical whitespace
// char is not always treated correctly for whatever reason.
foreach (preg_split("/\v|\n/", $contents, -1, PREG_SPLIT_NO_EMPTY) as $path) {
// No need to process folders as each changed file is checked.
if (substr($path, -1) === '/') {
continue;
}
// We need to check ignore rules ourself because they are
// not checked when processing a single file.
if ($phpcs->shouldProcessFile($path) === false) {
continue;
}
// Get the contents of each file, as it would be after this transaction.
$command = PHP_CODESNIFFER_SVNLOOK.' cat '.implode(' ', $values['svnArgs']).' '.escapeshellarg($path);
$handle = popen($command, 'r');
if ($handle === false) {
echo 'ERROR: Could not execute "'.$command.'"'.PHP_EOL.PHP_EOL;
exit(2);
}
$contents = stream_get_contents($handle);
fclose($handle);
$phpcs->processFile($path, $contents);
}//end foreach
return $this->printErrorReport(
$phpcs,
$values['reports'],
$values['showSources'],
$values['reportFile'],
$values['reportWidth']
);
}//end process()
/**
* Prints out the usage information for this script.
*
* #return void
*/
public function printUsage()
{
parent::printUsage();
echo PHP_EOL;
echo ' Each additional argument is passed to the `svnlook changed ...`'.PHP_EOL;
echo ' and `svnlook cat ...` commands. The report is printed on standard output,'.PHP_EOL;
echo ' however Subversion displays only standard error to the user, so in a'.PHP_EOL;
echo ' pre-commit hook, this script should be invoked as follows:'.PHP_EOL;
echo PHP_EOL;
echo ' '.basename($_SERVER['argv'][0]).' ... "$REPOS" -t "$TXN" >&2 || exit 1'.PHP_EOL;
}//end printUsage()
}//end class
$phpcs = new PHP_CodeSniffer_SVN_Hook();
$phpcs->checkRequirements();
$numErrors = $phpcs->process();
if ($numErrors !== 0) {
exit(1);
}
?>
And my pre-commit file from subversion:
#!/bin/sh
# PRE-COMMIT HOOK
#
# The pre-commit hook is invoked before a Subversion txn is
# committed. Subversion runs this hook by invoking a program
# (script, executable, binary, etc.) named 'pre-commit' (for which
# this file is a template), with the following ordered arguments:
#
# [1] REPOS-PATH (the path to this repository)
# [2] TXN-NAME (the name of the txn about to be committed)
#
# [STDIN] LOCK-TOKENS ** the lock tokens are passed via STDIN.
#
# If STDIN contains the line "LOCK-TOKENS:\n" (the "\n" denotes a
# single newline), the lines following it are the lock tokens for
# this commit. The end of the list is marked by a line containing
# only a newline character.
#
# Each lock token line consists of a URI-escaped path, followed
# by the separator character '|', followed by the lock token string,
# followed by a newline.
#
# The default working directory for the invocation is undefined, so
# the program should set one explicitly if it cares.
#
# If the hook program exits with success, the txn is committed; but
# if it exits with failure (non-zero), the txn is aborted, no commit
# takes place, and STDERR is returned to the client. The hook
# program can use the 'svnlook' utility to help it examine the txn.
#
# On a Unix system, the normal procedure is to have 'pre-commit'
# invoke other programs to do the real work, though it may do the
# work itself too.
#
# *** NOTE: THE HOOK PROGRAM MUST NOT MODIFY THE TXN, EXCEPT ***
# *** FOR REVISION PROPERTIES (like svn:log or svn:author). ***
#
# This is why we recommend using the read-only 'svnlook' utility.
# In the future, Subversion may enforce the rule that pre-commit
# hooks should not modify the versioned data in txns, or else come
# up with a mechanism to make it safe to do so (by informing the
# committing client of the changes). However, right now neither
# mechanism is implemented, so hook writers just have to be careful.
#
# Note that 'pre-commit' must be executable by the user(s) who will
# invoke it (typically the user httpd runs as), and that user must
# have filesystem-level permission to access the repository.
#
# On a Windows system, you should name the hook program
# 'pre-commit.bat' or 'pre-commit.exe',
# but the basic idea is the same.
#
# The hook program typically does not inherit the environment of
# its parent process. For example, a common problem is for the
# PATH environment variable to not be set to its usual value, so
# that subprograms fail to launch unless invoked via absolute path.
# If you're having unexpected problems with a hook program, the
# culprit may be unusual (or missing) environment variables.
#
# Here is an example hook script, for a Unix /bin/sh interpreter.
# For more examples and pre-written hooks, see those in
# the Subversion repository at
# http://svn.apache.org/repos/asf/subversion/trunk/tools/hook-scripts/ and
# http://svn.apache.org/repos/asf/subversion/trunk/contrib/hook-scripts/
REPOS="$1"
TXN="$2"
# Make sure that the log message contains some text.
SVNLOOK=/usr/local/bin/svnlook
$SVNLOOK log -t "$TXN" "$REPOS" | \
grep "[a-zA-Z0-9]" > /dev/null || exit 1
# Check that the author of this commit has the rights to perform
# the commit on the files and directories being modified.
commit-access-control.pl "$REPOS" "$TXN" commit-access-control.cfg || exit 1
C:\wamp\bin\php\php5.4.3\scripts\phpcs-svn-pre-commit "$REPOS" -t "$TXN" >&2 || exit 1
# All checks passed, so allow the commit.
exit 0
I am pretty new to both SVN and codesniffer so i am pretty much just following guides and running tests as i go along. Any advice or tips on getting this to work will be appreciated :)
UPDATE
I have managed to get some results out of my svn hook, it now however shows a list of errors based on the script. I am not familiar with the scripting language required for SVN so i have no clue how to solve these.
List of errors:
REPOS is not recognized as internal or external command
TXN is not recognized as internal or external command
unknown command '/usr/local/bin/svnlook'
$SVNLOOK is not recognized as internal or external command
Judging from your executable file path (C:\wamp\bin\php\php5.4.3\php.exe), I assume you are working on a Windows machine.
The pre-commit file in your question looks like a Linux Bash Shell script.
Bash has a different syntax from a batch file you would use under Windows.
I suggest you use a pre-commit.tmpl file made for a Windows machine.
You probably have onde of these in your hooks folder.
I use a Linux machine, so unfortunately I can't help you further on this search...
The pre-commit hook will look like this (excerpt taken from An Introduction to Subversion Hook Scripts on Windows):
#echo off
:: Stops commits that don't include a log message of at least 6 characters.
#echo off
setlocal
rem Subversion sends through the repository path and transaction id
set REPOS=%1
set TXN=%2
svnlook log %REPOS% -t %TXN% | findstr ...... > nul
if %errorlevel% gtr 0 (goto err) else exit 0
:err
echo --------------------------------------------------------------------------- 1>&2
echo Your commit has been blocked because it didn't include a log message. 1>&2
echo Do the commit again, this time with a log message that describes your changes. 1>&2
echo --------------------------------------------------------------------------- 1>&2
exit 1
Closed. This question does not meet Stack Overflow guidelines. It is not currently accepting answers.
We don’t allow questions seeking recommendations for books, tools, software libraries, and more. You can edit the question so it can be answered with facts and citations.
Closed 6 years ago.
Improve this question
For a project I am working on, we want to use git as a revision tracker for certain data we modify often. We are using php for the web frontend and we need a goo php git client to use. I have come across a handful on the internet and they all tend to have the same limitation...
There is no support for HTTP. We need to be able to push/pull to remote repositories. We also need to clone.
Ideally I am looking for something that does not use the git command (ie: wrapers to exec()) but I am willing to settle if the class works well. I have seen a C library which appears to do what I want, however the php language binding is incomplete and the http functions are labeled experimental.
Does anyone have any insight into using git and http through php?
https://github.com/kbjr/Git.php
Git.php is a wrapper class around git calls that uses proc_open instead of exec to run the commands. While it does not have push/pull methods, it does have a general run method for running custom git commands, so it could be used something like this:
$repo = Git::open('/path/to/repo');
$repo->run('push origin master');
It also does have methods for cloning (clone_to and clone_from which do local cloning and clone_remote for remote cloning).
One possibility is to use PHP's SSH library to perform those actions by connecting back to the web server?
Or I found this set of classes which allow you to you to clone and read other metadata over HTTP, but not push nor pull. However it could be a starting point if you're brave enough to extend them to do that. I can imagine it would be a lot of work to replicate these processes and keep them compliant with various server versions etc.
[UPDATED 23/03/2014, after receiving an upvote - thanks!]
I did get some way trying to implement the above (I was searching for an implementation, drew a blank so tried to write my own), and it was hard as I thought! I actually abandoned it as I found a simpler way to achieve this in a different architecture, but here's the class I wrote trying. It essentially works, but was brittle to the environmental variations I was working in (i.e. it doesn't cope very well with errors or problems).
It uses:
herzult/php-ssh
an ssh config file - a trick to simplify setting up authentication credentials in code
(Note - I had to quickly strip out a few details from the code to post it. So you'll need to fiddle about a bit to integrate it into your app/namespace etc.)
<?php
/**
* #author: scipilot
* #since: 31/12/2013
*/
/**
* Class GitSSH allows you to perform Git functions over an SSH session.
* i.e. you are using the command-line Git commands after SSHing to a host which has the Git client.
*
* You don't need to know about the SSH to use the class, other than the fact you will need access
* to a server via SSH which has the Git client installed. Its likely this is the local web server.
*
* This was made because PHP has no good native Git client.
*
* Requires herzult/php-ssh
*
* Example php-ssh config file would be
*
* <code>
* Host localhost
* User git
* IdentityFile id_rsa
*
* Host your.host.domain.com
* User whoever
* IdentityFile ~/.ssh/WhoeverGit
*</code>
*/
class GitSSH {
protected $config;
protected $session;
protected $sPath;
/**
* #var string
*/
protected $sConfigPath = '~/.ssh/config';
/**
* Connects to the specified host, ready for further commands.
*
* #param string $sHost Host (entry in the config file) to connect to.
* #param string $sConfigPath Optional; config file path. Defaults to ~/.ssh/config,
* which is probably inaccessible for web apps.
*/
function __construct($sHost, $sConfigPath=null){
\Log::info('New GitSSH '.$sHost.', '.$sConfigPath);
if(isset($sConfigPath)) $this->sConfigPath = $sConfigPath;
$this->config = new \Ssh\SshConfigFileConfiguration($this->sConfigPath, $sHost);
$this->session = new \Ssh\Session($this->config, $this->config->getAuthentication());
}
public function __destruct() {
$this->disconnect();
}
/**
* Thanks to Steve Kamerman, as there isn't a native disconnect.
*/
public function disconnect() {
$this->exec('echo "EXITING" && exit;');
$this->session = null;
}
/**
* Run a command (in the current working directory set by cd)
* #param $sCommand
* #return string
*/
protected function exec($sCommand) {
//echo "\n".$sCommand."\n";
$exec = $this->session->getExec();
$result = $exec->run('cd '.$this->sPath.'; '.$sCommand);
// todo: parse/scrape the result, return a Result object?
return $result;
}
/**
* CD to a folder. (This not an 'incremental' cd!)
* Devnote: we don't really execute the cd now, it's appended to other commands. Each command seems to re-login?
*
* #param string $sPath Absolute filesystem path, or relative from user home
*/
public function cd($sPath){
$this->sPath = $sPath;
// #todo this is useless! each command seems to run in a separate login?
//$result = $this->exec('cd'); // /; ls');
//return $result;
}
/**
* #return string
*/
public function ls(){
$result = $this->exec('ls ');
return $result;
}
public function gitAdd($sOptions=null, array $aFiles=null){
$result = $this->exec('git add '
.(empty($sOptions) ? '' : ' '.$sOptions)
.(empty($aFiles) ? '' : ' '.implode(' ', $aFiles))
);
return $result;
}
public function gitClone($sRepo, $sBranch=null, $sTarget=null){
\Log::info('GitSSH::clone '.$sRepo.', '.$sBranch.', '.$sTarget);
$result = $this->exec('git clone '
.(empty($sBranch) ? '' : ' --branch '.$sBranch)
.' '.$sRepo
.' '.$sTarget);
return $result;
}
public function gitCommit($sMessage, $sOptions=null, array $aFiles=null){
$result = $this->exec('git commit '
.'-m "'.addcslashes($sMessage, '"').'"'
.(empty($sOptions) ? '' : ' '.$sOptions)
.(empty($aFiles) ? '' : ' '.implode(' ', $aFiles))
);
return $result;
}
public function gitPull($sOptions=null, $sRepo=null, $sRefspec=null){
$result = $this->exec('git pull '
.(empty($sOptions) ? '' : ' '.$sOptions)
.(empty($sRepo) ? '' : ' '.$sRepo)
.(empty($sRefspec) ? '' : ' '.$sRefspec)
);
return $result;
}
public function gitPush($sOptions=null, $sRepo=null, $sRefspec=null){
$result = $this->exec('git push '
.(empty($sOptions) ? '' : ' '.$sOptions)
.(empty($sRepo) ? '' : ' '.$sRepo)
.(empty($sRefspec) ? '' : ' '.$sRefspec)
);
return $result;
}
/**
* #return string the raw result from git status
*/
public function gitStatus(){
$result = $this->exec('git status');
return $result;
}
}
This looks promising: http://gitphp.org (broken link; see an archived version)
I think that will do it for you. Here is the description of it:
GitPHP is a web frontend for git repositories. It emulates the look of standard gitweb, but is written in PHP and makes use of Smarty templates for customization. It has a couple extras, including syntax highlighting through the GeSHi PHP class and project category support. It works with standard git as well as msysgit on Windows.
Setup should be fairly simple – just extract the tarball where you want to install it, copy config/gitphp.conf.php.example to config/gitphp.conf.php, and set the projectroot in the conf to point to your directory where your bare git repositories are, and make the templates_c directory writeable by the webserver if it’s not already.
You can look through all the available options and defaults in config/gitphp.conf.defaults.php, and copy an option into your config file if you want to override the default. You can also copy config/projects.conf.php.example to config/projects.conf.php and edit it if you want more advanced control over your projects, such as defining categories for projects or loading projects from a text file. More detailed instructions are in the included README.
Note: if you’re upgrading your existing gitphp.conf.php will not be overwritten, but I recommend checking gitphp.conf.defaults.php for new configuration options that may have been added.
You can view the live copy running on this site.