Full Code of BrunoDeBarros/git-deploy-php for AI

master 5740bfa45569 cached
14 files
40.7 KB
10.0k tokens
39 symbols
1 requests
Download .txt
Repository: BrunoDeBarros/git-deploy-php
Branch: master
Commit: 5740bfa45569
Files: 14
Total size: 40.7 KB

Directory structure:
gitextract_0j4kcr8a/

├── .gitattributes
├── .gitignore
├── README.mkd
├── composer.json
├── deploy.ini
├── git-deploy
└── tools/
    ├── build.php
    ├── index.php
    └── src/
        ├── Config.php
        ├── Ftp.php
        ├── Git.php
        ├── Helpers.php
        ├── Server.php
        └── Sftp.php

================================================
FILE CONTENTS
================================================

================================================
FILE: .gitattributes
================================================
# Removes useless files from release ZIPs.
.gitattributes export-ignore
tools export-ignore
composer.json export-ignore

================================================
FILE: .gitignore
================================================
tools/vendor/*

================================================
FILE: README.mkd
================================================
git-deploy-php 2.0
==================

git-deploy-php allows quick and easy deployments of Git repositories to FTP or SFTP servers. You DO NOT need to have git installed on the server. Great for shared servers where you have no shell access, and to save bandwidth and time by only uploading the files that have changed.

Usage
-----

1. Drop `git-deploy` into your project.
2. Create the `deploy.ini` file (see below).
3. Run `php git-deploy` in your command line / terminal.

And there you go. It's that simple.

### deploy.ini

In the root directory of your project, create a `deploy.ini` file.

*Note: `deploy.ini` is the default file's name. You can name it anything you want (for example, `staging.ini`), and run `php git-deploy staging`. That serves as an easy way to separate the deployment to staging and production servers.*

    ; This is a sample deploy.ini file.
    
    [example]
    
    skip = false
    user = example
    pass = password    
    host = example.com
    port = 21
    path = /path/to/installation
    passive = true
    
    ; If that seemed too long for you, you can specify servers like this:
    [ftp://example:password@example.com:21/path/to/installation]

The first time it's executed, git-deploy will assume that your deployment server is empty, and will upload all the files in the git repository.
If you've already deployed your project's files previously, you have to create a REVISION file on your deployment server, containing the hash of the commit it's currently in.

To get the hash of the current commit, you can use:

    git rev-parse HEAD

Advanced Usage
--------------

### Using SFTP

To use SFTP, you have two options. With the long-form deploy.ini configuration, you just need to add `scheme = sftp`. With the short-form, you just need to change `ftp://` to `sftp://`.

If you want to use a private key to login instead of a password, you can do so by adding the following to your deploy.ini configuration:

    sftp_key = /path/to/key/file

### Revert a deployment

If you deployed using git-deploy and you want to go back to the previous deployment, all you need is `--revert`:

    php git-deploy --revert [your_ini_file_name]

If you use deploy.ini (i.e., if you didn't give the .ini file a custom name), then you can simply use:

    php git-deploy --revert

### Deploy a specific commit

    php git-deploy -r [your_commit_hash] [your_ini_file_name]

If you use deploy.ini (i.e., if you didn't give the .ini file a custom name), then you can simply use:

    php git-deploy -r [your_commit_hash]

Note: If you want to, instead of using a commit hash, you can use a tag, or any other valid reference.

### Deploy the latest commit from different branches

Sometimes you might need to deploy code from different branches to different servers (e.g. from the develop branch to the staging server, and from the master branch to the production server). This is easy to do. Add the following to your deploy.ini configuration:

    branch = develop

git-deploy will then always deploy the latest commit from the develop branch to the server.

### List files to upload and delete

Sometimes, you may just want to see what files are to be uploaded to the FTP server, and which ones are to be deleted. In this case, you can use `-l` (lowercase L):

    php git-deploy -l [your_ini_file_name]

If you use deploy.ini (i.e., if you didn't give the .ini file a custom name), then you can simply use:

    php git-deploy -l

Pretty simple, huh?

### Clean remote directories automatically

If you have directories you use for caching that you'd like to clear when you deploy a new commit, you can add the following to your .ini file:

	clean_directories[] = folder/to/clean
	clean_directories[] = another/folder

And git-deploy will empty those directories for you.

### Ignore files

If you have files that you don't want uploaded to your server, you can add the following to your .ini file:

	ignore_files[] = file/toignore.txt
	ignore_files[] = another/file/toignore.php

And git-deploy will ignore those files.

### Upload untracked files

If you have files that you're not tracking in your repository but that you'd still like to upload, you can add the following to your .ini file:

	upload_untracked[] = folder/to/upload
	upload_untracked[] = another/file/toignore.php

And git-deploy will automatically upload those files for you. This is super useful for things like Composer, which recommends that you don't track the vendor folder in your git repo. This way, you can have git-deploy upload the entire vendor folder to the server, and you won't need Composer installed on it.

### Deploy only if the repository is in sync with the remote

If the repository is maintained by more than a person and you are afraid someone makes a deploy when its local repository is not aligned with the remote, you can make git-deploy-php block the operation when this case occurs. To configure this behaviour, simply add to your .ini file the following:

    check_sync_with_remote = true

### Enable maintenance mode on your website for the duration of the deployment

If you want to take your website down with an "under maintenance" page to prevent users from seeing errors during the middle of a deployment, you can ask git-deploy to automatically turn maintenance mode on prior to deploying, and off at the end of the deployment.

It works by modifying a file specified by the `maintenance_file` option in your `deploy.ini`. It'll set that file's contents to `maintenance_on_value` at the start of the deployment and then set its contents to `maintenance_off_value` at the end of the deployment. For example:

    maintenance_file = 'maintenance.php'
    maintenance_on_value = '<?php $under_maintenance = true;?>'
    maintenance_off_value = '<?php $under_maintenance = false;?>'

Note: It's up to your application to detect whether or not maintenance mode is on by checking the `maintenance_file`, and proceed accordingly.

Some frameworks looks for a file at a specific location and shows it's maintenance page if the file exists disregarding the content of the file. The file can be deleted at the end of deployment by not setting `maintenance_off_value`.
This example works with Laravel 5:
    maintenance_file = 'storage/framework/down'
    maintenance_on_value = 'Down for maintenance'

How It Works
------------
git-deploy stores a file called REVISION on your server. This file contains the hash of the commit that you've deployed to that server. When you run git-deploy, it downloads that file and compares the commit reference in it with the commit you're trying to deploy to find out which files to upload.

git-deploy also stores a REVISION file for each submodule in your repository, as well as a PREVIOUS_REVISION file for both your repository and each submodule. This allows it to keep your submodules up-to-date, as well as to know which commit to go back to you when you run `php git-deploy --revert`.

Suggestions, questions and complaints.
----------

If you've got any suggestions, questions, or anything you don't like about git-deploy, [you should create an issue here](https://github.com/BrunoDeBarros/git-deploy-php/issues). Feel free to fork this project, if you want to contribute to it. 


================================================
FILE: composer.json
================================================
{
    "name": "brunodebarros/git-deploy-php",
    "description": "git-deploy-php is a simple php-based tool that deploys your Git repositories to FTP/SFTP servers, and keeps them updated automatically.",
    "keywords": [
        "git",
        "deploy",
        "ftp",
        "sftp",
        "php"
    ],
    "license": "MIT",
    "type": "library",
    "homepage": "http://brunodebarros.github.io/git-deploy-php/",
    "support": {
        "issues": "https://github.com/BrunoDeBarros/git-deploy-php/issues"
    },
    "authors": [
        {
            "name": "Bruno Moreira De Barros",
            "email": "bruno@terraduo.com",
            "homepage": "https://terraduo.com",
            "role": "Developer"
        },
        {
            "name": "Mangirdas Skripka",
            "email": "mangirdas@impresspages.org",
            "homepage": "http://www.impresspages.org",
            "role": "Developer"
        },
        {
            "name": "sbtsrbayer",
            "role": "Developer"
        },
        {
            "name": "Tijmen Brommet",
            "email": "tijmen@gmail.com",
            "role": "Developer"
        },
        {
            "name": "Michael Naurbjerg",
            "role": "Developer"
        },
        {
            "name": "Sergey Volkov",
            "email": "sergey.volkov.kh@yandex.ua",
            "role": "Developer"
        },
        {
            "name": "Jesse Dobbelaere",
            "email": "jesse@dobbelaere-ae.be",
            "homepage": "http://www.jessedobbelae.re",
            "role": "Developer"
        }
    ],
    "bin": [
        "git-deploy"
    ],
    "autoload": {
        "psr-4": {
            "Brunodebarros\\Gitdeploy\\": "tools/src/"
        }
    },
    "require-dev": {
        "phpseclib/phpseclib": "^2.0",
        "league/climate": "^3.2",
        "league/flysystem": "^1.0",
        "phpunit/phpunit": "^5.1"
    },
    "config": {
        "vendor-dir": "tools/vendor",
        "preferred-install": "dist",
        "sort-packages": true,
        "optimize-autoloader": false
    },
    "scripts": {
        "post-autoload-dump": [
            "php tools/build.php"
        ]
    },
    "require": {
        "ext-ftp": "*"
    }
}


================================================
FILE: deploy.ini
================================================
; This is a sample deploy.ini file. 
;
; NOTES:
;
; 1. Don't forget that each server has to have a section title (e.g. [example]).
; 2. Don't forget to wrap your password around quotes.
; 3. If you don't specify 'pass', git-deploy-php will let you enter it in the terminal.

[example]

skip = false
user = "example"
pass = "password"
host = "example.com"
port = 21
path = "/path/to/installation"
passive = true


================================================
FILE: tools/build.php
================================================
#!/usr/bin/env php
<?php

class NonProjectFilesFilter extends RecursiveFilterIterator {
    public function accept() {
        if ($this->hasChildren()) {
            return true;
        } else {
            /** @var SplFileInfo $current */
            $current = $this->current();
            $realpath = substr($current->getRealPath(), strlen(dirname(__FILE__) . DIRECTORY_SEPARATOR));
            $extension = $current->getExtension();
            $valid_prefixes = ["src/", "vendor/composer/", "vendor/league/", "vendor/phpseclib/", "vendor/seld/", "vendor/myclabs/"];

            if ($realpath == "vendor/autoload.php") {
                return true;
            }

            foreach ($valid_prefixes as $prefix) {
                if (substr($realpath, 0, strlen($prefix)) == $prefix && $extension == "php") {
                    return true;
                }
            }

            return false;
        }
    }
}

if (ini_get("phar.readonly")) {
    echo "You need to set the 'phar.readonly' option to 'Off' in your php.ini file (" . php_ini_loaded_file() . ")" . PHP_EOL;
} else {
    $phar = new Phar('git-deploy.phar', 0, 'git-deploy');
    $iterator = new NonProjectFilesFilter(new RecursiveDirectoryIterator(dirname(__FILE__), FilesystemIterator::SKIP_DOTS));
    $d = $phar->buildFromIterator(new RecursiveIteratorIterator($iterator), dirname(__FILE__));
    $phar->setStub(file_get_contents(dirname(__FILE__) . "/index.php"));
    unset($phar);
    $path_to_git_deploy = dirname(__FILE__) . "/../git-deploy";
    @unlink($path_to_git_deploy);
    rename('git-deploy.phar', $path_to_git_deploy);
    chmod($path_to_git_deploy, 0755);
    echo "Built git-deploy successfully!" . PHP_EOL;
}


================================================
FILE: tools/index.php
================================================
#!/usr/bin/env php
<?php

ini_set('memory_limit', '-1');
error_reporting(E_ALL ^ E_DEPRECATED);
Phar::mapPhar("git-deploy");

/**
 * PSR-4 autoloader.
 *
 * @param string $class The fully-qualified class name.
 * @return void
 */
spl_autoload_register(function ($class) {

    // project-specific namespace prefix
    $prefix = 'Brunodebarros\\Gitdeploy\\';

    // base directory for the namespace prefix
    $base_dir = 'phar://git-deploy/src/';

    // does the class use the namespace prefix?
    $len = strlen($prefix);
    if (strncmp($prefix, $class, $len) !== 0) {
        // no, move to the next registered autoloader
        return;
    }

    // get the relative class name
    $relative_class = substr($class, $len);

    // replace the namespace prefix with the base directory, replace namespace
    // separators with directory separators in the relative class name, append
    // with .php
    $file = $base_dir . str_replace('\\', '/', $relative_class) . '.php';

    // if the file exists, require it
    if (file_exists($file)) {
        require $file;
    }
});

require 'phar://git-deploy/vendor/autoload.php';

$args = \Brunodebarros\Gitdeploy\Config::getArgs();
$servers = \Brunodebarros\Gitdeploy\Config::getServers($args['config_file']);
$git = new \Brunodebarros\Gitdeploy\Git($args['repo_path']);

foreach ($servers as $server) {
    if ($args['revert']) {
        $server->revert($git, $args['list_only']);
    } else {
        $server->deploy($git, $git->interpret_target_commit($args['target_commit'], $server->server['branch']), false, $args['list_only']);
    }
}

__HALT_COMPILER();


================================================
FILE: tools/src/Config.php
================================================
<?php

namespace Brunodebarros\Gitdeploy;

use Brunodebarros\Gitdeploy\Helpers;

class Config {

    public static function getArgs() {
        $argv = $_SERVER['argv'];

        $deploy = dirname(__FILE__) . DIRECTORY_SEPARATOR . 'deploy.ini';
        $commands = array('-l', '-r', '-c', '-d', '--revert', '--log', '--repo');

        $deploy_file = isset($argv[1]) ? end($argv) : "deploy.ini";

        if (!in_array($deploy_file, $commands)) {
            $deploy = $deploy_file . (substr($deploy_file, -4) === '.ini' ? '' : '.ini');
        }

        $opts = getopt("lr:d:c:", array("revert", "log::", "repo:"));

        if (isset($opts['log'])) {
            define('WRITE_TO_LOG', $opts['revert'] ? $opts['revert'] : 'git_deploy_php_log.txt');
        }

        if (isset($opts['d'])) {
            $deploy = $opts['d'];
        }

        if (isset($opts['c'])) {
            $opts['r'] = $opts['c'];
        }

        if (isset($opts['repo'])) {
            $repo_path = $opts['repo'];
        } else {
            $repo_path = getcwd() . DIRECTORY_SEPARATOR;
        }

        return array(
            'config_file' => $deploy,
            'target_commit' => isset($opts['r']) ? $opts['r'] : 'HEAD',
            'list_only' => isset($opts['l']),
            'revert' => isset($opts['revert']),
            'repo_path' => $repo_path,
        );
    }

    public static function getServers($config_file) {
        $servers = @parse_ini_file($config_file, true);
        $return = array();

        if (!$servers) {
            Helpers::error("File '$config_file' is not a valid .ini file.");
        } else {
            foreach ($servers as $uri => $options) {
                if (stristr($uri, "://") !== false) {
                    $options = array_merge($options, parse_url($uri));
                }

                # Throw in some default values, in case they're not set.
                $options = array_merge(array(
                    'skip' => false,
                    'scheme' => 'ftp',
                    'host' => '',
                    'user' => '',
                    'branch' => null,
                    'port' => 21,
                    'path' => '/',
                    'passive' => true,
                    'clean_directories' => array(),
                    'ignore_files' => array(),
                    'ignore_directories' => array(),
                    'upload_untracked' => array(),
                    'check_sync_with_remote' => false,
                    'remote_branch' => null
                ), $options);

                if ($options['check_sync_with_remote'])
                {
                    if (empty($options['remote_branch']))
                    {
                        $options['remote_branch'] = (!empty($options['branch'])) ?  "origin/".$options['branch'] : null;
                    }
                }

                if (!isset($options['pass']) && !isset($options['sftp_key'])) {
                    $options['pass'] = self::promptPassword();
                }

                if (isset($options['sftp_key'])) {
                    if (substr($options['sftp_key'], 0, 2) == "~/") {
                        $options['sftp_key'] = $_SERVER['HOME'] . substr($options['sftp_key'], 1);
                    }
                }

                if ($options['skip']) {
                    continue;
                } else {
                    unset($options['skip']);
                    $type = "Brunodebarros\\Gitdeploy\\" . ucfirst(strtolower($options['scheme']));
                    $return[$uri] = new $type($options, $config_file);
                }
            }
        }

        return $return;
    }

    public static function promptPassword() {
        $prompt = 'Enter ftp password: ';

        $command = "/usr/bin/env bash -c 'echo OK'";
        if (rtrim(shell_exec($command)) !== 'OK') {
            trigger_error("Can't invoke bash");
            return;
        }

        $command = "/usr/bin/env bash -c 'read -s -p \""
            . addslashes($prompt)
            . "\" mypassword && echo \$mypassword'";
        $password = rtrim(shell_exec($command));
        echo "\n";

        return $password;
    }

}


================================================
FILE: tools/src/Ftp.php
================================================
<?php

namespace Brunodebarros\Gitdeploy;

use Brunodebarros\Gitdeploy\Helpers;

class Ftp extends Server {

    public function connect($test = false) {
        if (!extension_loaded('ftp')) {
            Helpers::error("You need the FTP extension to be enabled if you want to deploy via FTP.");
        }

        if (!$this->connection or $test) {
            $server = $this->server;
            $this->connection = @ftp_connect($server['host'], $server['port'], 30);

            if (!$this->connection) {
                Helpers::error("Could not connect to {$this->host}");
            } else {
                if (!@ftp_login($this->connection, $server['user'], $server['pass'])) {
                    Helpers::error("Could not login to {$this->host}");
                }

                ftp_pasv($this->connection, $server['passive']);

                if (!ftp_chdir($this->connection, $server['path'])) {
                    Helpers::error("Could not change the directory to {$server['path']} on {$this->host}");
                }
            }

            Helpers::logmessage("Connected to: {$this->host}");
            $this->current_commit = $this->get_file('REVISION', true);
        }

        if ($test) {
            $this->disconnect();
        }
    }

    public function disconnect() {
        ftp_close($this->connection);
        $this->connection = null;
        Helpers::logmessage("Disconnected from: {$this->host}");
    }

    public function get_file($file, $ignore_if_error = false) {
        $this->connect();

        $tmpFile = tempnam(sys_get_temp_dir(), 'GITDEPLOYPHP');

        if ($ignore_if_error) {
            $result = @ftp_get($this->connection, $tmpFile, $file, FTP_BINARY);
        } else {
            # Display whatever error PHP throws.
            $result = ftp_get($this->connection, $tmpFile, $file, FTP_BINARY);
        }

        if ($result) {
            return file_get_contents($tmpFile);
        } else {
            # Couldn't get the file. I assume it's because the file didn't exist.
            if ($ignore_if_error) {
                return false;
            } else {
                Helpers::error("Failed to retrieve '$file'.");
            }
        }
    }

    public function set_file($file, $contents, $die_if_fail = false) {
        $this->connect();

        # Make sure the folder exists in the FTP server.

        $dir = explode("/", dirname($file));
        $dir_part_count = count($dir);
        $path = "";

        for ($i = 0; $i < $dir_part_count; $i++) {
            $path .= $dir[$i] . '/';

            if (!isset($this->existing_paths_cache[$path])) {
                $origin = ftp_pwd($this->connection);

                if (!@ftp_chdir($this->connection, $path)) {
                    if (!@ftp_mkdir($this->connection, $path)) {
                        Helpers::error("Failed to create the directory '$path'. Upload to this server cannot continue.");
                    } else {
                        Helpers::logmessage("Created directory: $path");
                        $this->existing_paths_cache[$path] = true;
                    }
                } else {
                    $this->existing_paths_cache[$path] = true;
                }

                ftp_chdir($this->connection, $origin);
            }
        }

        $tmpFile = tempnam(sys_get_temp_dir(), 'GITDEPLOYPHP');
        file_put_contents($tmpFile, $contents);
        $uploaded = ftp_put($this->connection, $file, $tmpFile, FTP_BINARY);

        if (!$uploaded) {
            if ($die_if_fail) {
                Helpers::error("Failed to upload {$file}. Deployment will stop to allow you to check what went wrong.");
            } else {
                # Try deleting the file and reuploading.
                # This resolves a CHMOD issue with some FTP servers.
                $this->unset_file($file);
                $this->set_file($file, $contents, true);
            }
        } else {
            Helpers::logmessage("Uploaded: $file");
            return true;
        }
    }

    protected function recursive_remove($file_or_directory, $die_if_fail = false) {
        $this->connect();

        if (!(@ftp_rmdir($this->connection, $file_or_directory) || @ftp_delete($this->connection, $file_or_directory))) {

            if ($die_if_fail) {
                return false;
            }

            $filelist = ftp_nlist($this->connection, $file_or_directory);

            foreach ($filelist as $file) {
                if ($file != '.' && $file != '..') {
                    $this->recursive_remove($file);
                }
            }

            $this->recursive_remove($file_or_directory, true);
        }
    }

    public function mkdir($file) {
        $this->connect();

        ftp_mkdir($this->connection, $file);
        Helpers::logmessage("Created directory: $file");
    }

    public function unset_file($file) {
        $this->connect();

        $this->recursive_remove($file);
        Helpers::logmessage("Deleted: $file");
    }

}


================================================
FILE: tools/src/Git.php
================================================
<?php

namespace Brunodebarros\Gitdeploy;

use Brunodebarros\Gitdeploy\Helpers;

class Git {

    public static $git_executable_path = "git";
    public $repo_path;

    public function __construct($repo_path) {
        $this->repo_path = rtrim($repo_path, '/') . '/';

        # Test if has Git in cmd.
        if (stristr($this->exec("--version"), "git version") === false) {
            Helpers::error("The command '" . self::$git_executable_path . "' was not found.");
        }
    }

    public function interpret_target_commit($target_commit, $branch = null) {
        if ($branch !== null) {
            if ($target_commit == "HEAD") {
                # Get the HEAD commit of the branch specified in the deploy.ini
                $target_commit = $branch;
            }
        }

        return $this->exec("rev-parse $target_commit");
    }

    public function get_changes($target_commit, $current_commit) {

        if (file_exists(".gitmodules")) {
            $submodules = parse_ini_file(".gitmodules", true);
        } else {
            $submodules = array();
        }
        $submodule_paths = array();

        foreach ($submodules as $submodule) {
            $submodule_paths[] = $submodule['path'];
        }

        if (!empty($current_commit)) {
            $command = "diff --no-renames --name-status {$current_commit} {$target_commit}";
        } else {
            $command = "ls-files";
        }

        $return = array(
            'upload' => array(),
            'delete' => array(),
            'submodules' => $submodule_paths,
        );

        $command = str_replace(array("\n", "\r\n"), '', $command);
        $result = $this->exec($command);

        if (empty($result)) {
            # Nothing has changed.
            return $return;
        }

        $result = explode("\n", $result);

        if (!empty($current_commit)) {
            foreach ($result as $line) {
                if ($line[0] == 'A' or $line[0] == 'C' or $line[0] == 'M') {
                    $path = trim(substr($line, 1, strlen($line)));
                    $return['upload'][$path] = $this->get_file_contents("$target_commit:$path");
                } elseif ($line[0] == 'D') {
                    $return['delete'][] = trim(substr($line, 1, strlen($line)));
                } elseif ($line[0] == 'R') {
                    $details = preg_split("/\\s+/", $line);
                    $return['delete'][] = $details[1];
                    $return['upload'][] = $details[2];
                } elseif (stristr($line, "fatal: bad object") !== false) {
                    $commit = trim(str_ireplace("fatal: bad object", "", $line));
                    Helpers::error("The commit '$commit' does not exist in this clone of the repo.\nFor more information, check out: https://help.github.com/articles/commit-exists-on-github-but-not-in-my-local-clone/");
                } else {
                    Helpers::error("Unknown git-diff status: {$line[0]} (LINE: $line)");
                }
            }
        } else {
            foreach ($result as $file) {
                if (!in_array($file, $submodule_paths)) {
                    $return['upload'][$file] = $this->get_file_contents("$target_commit:$file");
                }
            }
        }

        return $return;
    }

    // http://stackoverflow.com/a/3278427
    public function get_status_towards_remote($local_branch, $remote_branch)
    {
        if (empty($local_branch))
        {
            $local_branch = "@";
        }

        if (empty($remote_branch))
        {
            $remote_branch = "@{u}";
        }

        $this->exec("remote update");

        $local = $this->exec("rev-parse ".$local_branch);
        $remote = $this->exec("rev-parse ".$remote_branch);
        $base = $this->exec("merge-base ".$local_branch." ".$remote_branch);

        if ($local == $remote)
        {
            $status = "up-to-date";
        }
        else if ($local == $base)
        {
            $status = "pull-needed";
        }
        else if ($remote == $base)
        {
            $status = "push-needed";
        }
        else
        {
            $status = "diverged";
        }

        return $status;
    }

    protected function get_file_contents($path) {
        $temp = tempnam(sys_get_temp_dir(), "git-deploy-");
        $this->exec('show ' . escapeshellarg($path), "> \"$temp\"");
        return file_get_contents($temp);
    }

    protected function exec($command, $suffix = "") {
        if (chdir($this->repo_path)) {
            $console = trim(shell_exec(self::$git_executable_path . " " . $command . " 2>&1 " . $suffix));
            return $console;
        } else {
            Helpers::error("Unable to access the git repository's folder.");
        }
    }

}


================================================
FILE: tools/src/Helpers.php
================================================
<?php

namespace Brunodebarros\Gitdeploy;

class Helpers {

    public static function logmessage($message) {
        static $log_handle = null;

        $log = "[" . @date("Y-m-d H:i:s O") . "] " . $message . PHP_EOL;
        if (defined("WRITE_TO_LOG")) {
            if ($log_handle === null) {
                $log_handle = fopen(WRITE_TO_LOG, 'a');
            }

            fwrite($log_handle, $log);
        }

        echo "\033[0m".$log;
    }

    public static function error($message, $die = true) {
        self::logmessage("\033[0;31mERROR: $message\033[0m");

        if ($die)
        {
            die();
        }
    }

    public static function get_recursive_file_list($folder, $prefix = '') {

        # Add trailing slash
        $folder = (substr($folder, strlen($folder) - 1, 1) == '/') ? $folder : $folder . '/';

        $return = array();

        foreach (self::clean_scandir($folder) as $file) {
            if (is_dir($folder . $file)) {
                $return = array_merge($return, self::get_recursive_file_list($folder . $file, $prefix . $file . '/'));
            } else {
                $return[] = $prefix . $file;
            }
        }

        return $return;
    }

    public static function clean_scandir($folder, $ignore = array()) {
        $ignore[] = '.';
        $ignore[] = '..';
        $ignore[] = '.DS_Store';
        $return = array();

        foreach (scandir($folder) as $file) {
            if (!in_array($file, $ignore)) {
                $return[] = $file;
            }
        }

        return $return;
    }

}


================================================
FILE: tools/src/Server.php
================================================
<?php

namespace Brunodebarros\Gitdeploy;

use Brunodebarros\Gitdeploy\Helpers;

abstract class Server {

    public $connection;
    public $current_commit;
    public $host;
    public $existing_paths_cache;
    public $clean_directories;
    public $ignore_files;
    public $ignore_directories;
    public $upload_untracked;
    public $server;

    public function __construct($server, $deploy_script = 'deploy.ini') {
        $this->server = $server;
        $this->clean_directories = $server['clean_directories'];
        $this->ignore_files = array_merge(array(
            '.gitignore', '.gitattributes', '.gitmodules', 'deploy.ini', 'git-deploy', $deploy_script,
        ), $server['ignore_files']);
        $this->ignore_directories = $server['ignore_directories'];
        $this->upload_untracked = $server['upload_untracked'];
        $this->host = "{$server['scheme']}://{$server['user']}@{$server['host']}:{$server['port']}{$server['path']}";
        $this->connect(true);
    }

    public function deploy(Git $git, $target_commit, $is_revert = false, $list_only = false) {

        if ($this->server['check_sync_with_remote'])
        {
            $statusTowardsRemote = $git->get_status_towards_remote($this->server['branch'], $this->server['remote_branch']);

            if ($statusTowardsRemote != "up-to-date")
            {
                Helpers::error("In order to deploy, local and remote repository must be in sync.", false);
                if ($statusTowardsRemote == "push-needed")
                {
                    Helpers::logmessage("Push your changes to the remote and come back here to retry.");
                }
                else if ($statusTowardsRemote == "pull-needed")
                {
                    Helpers::logmessage("Pull the changes from the remote and come back here to retry.");
                }
                else if ($statusTowardsRemote == "diverged")
                {
                    Helpers::logmessage("Pull the changes from the remote, merge them with yours and then push to the repository. Thereafter come back here to retry.");
                }
                return;
            }
        }


        if ($target_commit == $this->current_commit) {
            Helpers::logmessage("Nothing to update on: $this->host");
            return;
        }

        if ($list_only) {
            Helpers::logmessage("DETECTED '-l'. NO FILES ARE BEING UPLOADED / DELETED, THEY ARE ONLY BEING LISTED.");
        }

        Helpers::logmessage("Started working on: {$this->host}");

        if ($is_revert) {
            Helpers::logmessage("Reverting server from " . substr($this->current_commit, 0, 6) . " to " . substr($target_commit, 0, 6) . "...");
        } elseif (empty($this->current_commit)) {
            Helpers::logmessage("Deploying to server for the first time...");
        } else {
            Helpers::logmessage("Updating server from " . substr($this->current_commit, 0, 6) . " to " . substr($target_commit, 0, 6) . "...");
        }

        # Get files between $commit and REVISION
        $changes = $git->get_changes($target_commit, $this->current_commit);

        foreach ($changes['upload'] as $file => $contents) {
            if (in_array($file, $this->ignore_files)) {
                unset($changes['upload'][$file]);
            }
            foreach ($this->ignore_directories as $ignoreDir) {
                if (strpos($file, $ignoreDir) !== false) {
                    unset($changes['upload'][$file]);
                    break;
                }
            }
        }

        foreach ($this->upload_untracked as $file) {
            if (file_exists($git->repo_path . $file)) {
                if (is_dir($git->repo_path . $file)) {
                    foreach (Helpers::get_recursive_file_list($git->repo_path . $file, $file . "/") as $buffer) {
                        $changes['upload'][$buffer] = file_get_contents($git->repo_path . $buffer);
                    }
                } else {
                    $changes['upload'][$file] = file_get_contents($git->repo_path . $file);
                }
            }
        }

        $submodule_meta = array();

        foreach ($changes['submodules'] as $submodule) {
            Helpers::logmessage($submodule);
            $current_subcommit = $this->get_file($submodule . '/REVISION', true);
            $subgit = new \Brunodebarros\Gitdeploy\Git($git->repo_path . $submodule . "/");
            $target_subcommit = $subgit->interpret_target_commit("HEAD");
            $subchanges = $subgit->get_changes($target_subcommit, $current_subcommit);

            $submodule_meta[$submodule] = array(
                'target_subcommit' => $target_subcommit,
                'current_subcommit' => $current_subcommit,
            );

            foreach ($subchanges['upload'] as $file => $contents) {
                $changes['upload'][$submodule . "/" . $file] = $contents;
            }

            foreach ($subchanges['delete'] as $file => $contents) {
                $changes['delete'][$submodule . "/" . $file] = $contents;
            }
        }

        $count_upload = count($changes['upload']);
        $count_delete = count($changes['delete']);

        if ($count_upload == 0 && $count_delete == 0) {
            Helpers::logmessage("Nothing to update on: $this->host");
            return;
        }

        if ($count_upload > 0) {
            $count_upload = $count_upload + 2;
        }

        Helpers::logmessage("Will upload $count_upload file" . ($count_upload == 1 ? '' : 's') . ".");
        Helpers::logmessage("Will delete $count_delete file" . ($count_delete == 1 ? '' : 's') . ".");

        if (isset($this->server['maintenance_file'])) {
            $this->set_file($this->server['maintenance_file'], $this->server['maintenance_on_value']);
            Helpers::logmessage("Turned maintenance mode on.");
        }

        foreach ($changes['upload'] as $file => $contents) {
            if ($list_only) {
                Helpers::logmessage("Uploaded: $file");
            } else {
                if (!in_array($file, $changes['submodules'])) {
                    $this->set_file($file, $contents);
                }
            }
        }

        foreach ($changes['delete'] as $file) {
            if ($list_only) {
                Helpers::logmessage("Deleted: $file");
            } else {
                $this->unset_file($file);
            }
        }

        foreach ($this->clean_directories as $directory) {
            $this->unset_file($directory);
            $this->mkdir($directory);
        }

        foreach ($changes['submodules'] as $submodule) {
            $this->set_file($submodule . '/REVISION', $submodule_meta[$submodule]['target_subcommit']);
            $this->set_file($submodule . '/PREVIOUS_REVISION', (empty($submodule_meta[$submodule]['current_subcommit']) ? $submodule_meta[$submodule]['target_subcommit'] : $submodule_meta[$submodule]['current_subcommit']));
        }

        $this->set_current_commit($target_commit, $list_only);
    }

    public function revert($git, $list_only = false) {
        $target_commit = $this->get_file('PREVIOUS_REVISION', true);
        if (empty($target_commit)) {
            Helpers::error("Cannot revert: {$this->host} server has no PREVIOUS_REVISION file.");
        } else {
            $this->deploy($git, $target_commit, true, $list_only);
        }
    }

    protected function set_current_commit($target_commit, $list_only = false) {
        if (!$list_only) {
            $this->set_file('REVISION', $target_commit);
            $this->set_file('PREVIOUS_REVISION', (empty($this->current_commit) ? $target_commit : $this->current_commit));
        }

        if (isset($this->server['maintenance_file'])) {
            if (isset($this->server['maintenance_off_value'])) {
                $this->set_file($this->server['maintenance_file'], $this->server['maintenance_off_value']);
            } else {
                $this->unset_file($this->server['maintenance_file']);
            }
            Helpers::logmessage("Turned maintenance mode off.");
        }

        Helpers::logmessage("Finished working on: {$this->host}");
        $this->disconnect();
    }

}


================================================
FILE: tools/src/Sftp.php
================================================
<?php

namespace Brunodebarros\Gitdeploy;

use Brunodebarros\Gitdeploy\Helpers;

class Sftp extends Server {

    public function connect($test = false) {
        if (!$this->connection or $test) {
            $server = $this->server;

            $this->connection = new \phpseclib\Net\SFTP($server['host'], $server['port'], 10);
            $logged_in = false;

            if (isset($server['sftp_key'])) {
                $key = new \phpseclib\Crypt\RSA();
                if (isset($server['pass']) && !empty($server['pass'])) {
                    $key->setPassword($server['pass']);
                }
                $key->loadKey(file_get_contents($server['sftp_key']));
                $logged_in = $this->connection->login($server['user'], $key);

                if (!$logged_in) {
                    Helpers::error("Could not login to {$this->host}. It may be because the key requires a passphrase, which you need to specify it as the 'pass' attribute.");
                }
            } else {
                $logged_in = $this->connection->login($server['user'], $server['pass']);

                if (!$logged_in) {
                    Helpers::error("Could not login to {$this->host}");
                }
            }

            if (!$this->connection->chdir($server['path'])) {
                Helpers::error("Could not change the directory to {$server['path']} on {$this->host}");
            }

            Helpers::logmessage("Connected to: {$this->host}");
            $this->current_commit = $this->get_file('REVISION', true);
        }

        if ($test) {
            $this->disconnect();
        }
    }

    public function disconnect() {
        $this->connection->disconnect();
        $this->connection = null;
        Helpers::logmessage("Disconnected from: {$this->host}");
    }

    public function get_file($file, $ignore_if_error = false) {
        $this->connect();

        $contents = $this->connection->get($file);
        if ($contents) {
            return $contents;
        } else {
            # Couldn't get the file. I assume it's because the file didn't exist.
            if ($ignore_if_error) {
                return false;
            } else {
                Helpers::error("Failed to retrieve '$file'.");
            }
        }
    }

    public function set_file($file, $contents) {
        $this->connect();

        $file = $file;
        $dir = explode("/", dirname($file));
        $dir_part_count = count($dir);
        $path = "";

        for ($i = 0; $i < $dir_part_count; $i++) {
            $path .= $dir[$i] . '/';

            if (!isset($this->existing_paths_cache[$path])) {
                $origin = $this->connection->pwd();

                if (!$this->connection->chdir($path)) {
                    if (!$this->connection->mkdir($path)) {
                        Helpers::error("Failed to create the directory '$path'. Upload to this server cannot continue.");
                    } else {
                        Helpers::logmessage("Created directory: $path");
                        $this->existing_paths_cache[$path] = true;
                    }
                } else {
                    $this->existing_paths_cache[$path] = true;
                }

                $this->connection->chdir($origin);
            }
        }

        if ($this->connection->put($file, $contents)) {
            Helpers::logmessage("Uploaded: $file");
            return true;
        } else {
            Helpers::error("Failed to upload {$file}. Deployment will stop to allow you to check what went wrong.");
        }
    }

    protected function recursive_remove($file_or_directory, $if_dir = false) {
        $this->connect();

        $parent = dirname($file_or_directory);
        if ($this->connection->delete($file_or_directory, $if_dir)) {
            $filelist = $this->connection->nlist($parent);
            foreach ($filelist as $file) {
                if ($file != '.' && $file != '..') {
                    return false;
                }
            }

            $this->recursive_remove($parent, true);
        }
    }

    public function mkdir($file) {
        $this->connect();

        $this->connection->mkdir($file);
        Helpers::logmessage("Created directory: $file");
    }

    public function unset_file($file) {
        $this->connect();

        $this->recursive_remove($file, false);
        Helpers::logmessage("Deleted: $file");
    }

}
Download .txt
gitextract_0j4kcr8a/

├── .gitattributes
├── .gitignore
├── README.mkd
├── composer.json
├── deploy.ini
├── git-deploy
└── tools/
    ├── build.php
    ├── index.php
    └── src/
        ├── Config.php
        ├── Ftp.php
        ├── Git.php
        ├── Helpers.php
        ├── Server.php
        └── Sftp.php
Download .txt
SYMBOL INDEX (39 symbols across 7 files)

FILE: tools/build.php
  class NonProjectFilesFilter (line 4) | class NonProjectFilesFilter extends RecursiveFilterIterator {
    method accept (line 5) | public function accept() {

FILE: tools/src/Config.php
  class Config (line 7) | class Config {
    method getArgs (line 9) | public static function getArgs() {
    method getServers (line 50) | public static function getServers($config_file) {
    method promptPassword (line 111) | public static function promptPassword() {

FILE: tools/src/Ftp.php
  class Ftp (line 7) | class Ftp extends Server {
    method connect (line 9) | public function connect($test = false) {
    method disconnect (line 41) | public function disconnect() {
    method get_file (line 47) | public function get_file($file, $ignore_if_error = false) {
    method set_file (line 71) | public function set_file($file, $contents, $die_if_fail = false) {
    method recursive_remove (line 120) | protected function recursive_remove($file_or_directory, $die_if_fail =...
    method mkdir (line 141) | public function mkdir($file) {
    method unset_file (line 148) | public function unset_file($file) {

FILE: tools/src/Git.php
  class Git (line 7) | class Git {
    method __construct (line 12) | public function __construct($repo_path) {
    method interpret_target_commit (line 21) | public function interpret_target_commit($target_commit, $branch = null) {
    method get_changes (line 32) | public function get_changes($target_commit, $current_commit) {
    method get_status_towards_remote (line 97) | public function get_status_towards_remote($local_branch, $remote_branch)
    method get_file_contents (line 135) | protected function get_file_contents($path) {
    method exec (line 141) | protected function exec($command, $suffix = "") {

FILE: tools/src/Helpers.php
  class Helpers (line 5) | class Helpers {
    method logmessage (line 7) | public static function logmessage($message) {
    method error (line 22) | public static function error($message, $die = true) {
    method get_recursive_file_list (line 31) | public static function get_recursive_file_list($folder, $prefix = '') {
    method clean_scandir (line 49) | public static function clean_scandir($folder, $ignore = array()) {

FILE: tools/src/Server.php
  class Server (line 7) | abstract class Server {
    method __construct (line 19) | public function __construct($server, $deploy_script = 'deploy.ini') {
    method deploy (line 31) | public function deploy(Git $git, $target_commit, $is_revert = false, $...
    method revert (line 177) | public function revert($git, $list_only = false) {
    method set_current_commit (line 186) | protected function set_current_commit($target_commit, $list_only = fal...

FILE: tools/src/Sftp.php
  class Sftp (line 7) | class Sftp extends Server {
    method connect (line 9) | public function connect($test = false) {
    method disconnect (line 48) | public function disconnect() {
    method get_file (line 54) | public function get_file($file, $ignore_if_error = false) {
    method set_file (line 70) | public function set_file($file, $contents) {
    method recursive_remove (line 107) | protected function recursive_remove($file_or_directory, $if_dir = fals...
    method mkdir (line 123) | public function mkdir($file) {
    method unset_file (line 130) | public function unset_file($file) {
Condensed preview — 14 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (44K chars).
[
  {
    "path": ".gitattributes",
    "chars": 119,
    "preview": "# Removes useless files from release ZIPs.\n.gitattributes export-ignore\ntools export-ignore\ncomposer.json export-ignore"
  },
  {
    "path": ".gitignore",
    "chars": 14,
    "preview": "tools/vendor/*"
  },
  {
    "path": "README.mkd",
    "chars": 7258,
    "preview": "git-deploy-php 2.0\n==================\n\ngit-deploy-php allows quick and easy deployments of Git repositories to FTP or SF"
  },
  {
    "path": "composer.json",
    "chars": 2215,
    "preview": "{\n    \"name\": \"brunodebarros/git-deploy-php\",\n    \"description\": \"git-deploy-php is a simple php-based tool that deploys"
  },
  {
    "path": "deploy.ini",
    "chars": 411,
    "preview": "; This is a sample deploy.ini file. \n;\n; NOTES:\n;\n; 1. Don't forget that each server has to have a section title (e.g. ["
  },
  {
    "path": "tools/build.php",
    "chars": 1711,
    "preview": "#!/usr/bin/env php\n<?php\n\nclass NonProjectFilesFilter extends RecursiveFilterIterator {\n    public function accept() {\n "
  },
  {
    "path": "tools/index.php",
    "chars": 1615,
    "preview": "#!/usr/bin/env php\n<?php\n\nini_set('memory_limit', '-1');\nerror_reporting(E_ALL ^ E_DEPRECATED);\nPhar::mapPhar(\"git-deplo"
  },
  {
    "path": "tools/src/Config.php",
    "chars": 4207,
    "preview": "<?php\n\nnamespace Brunodebarros\\Gitdeploy;\n\nuse Brunodebarros\\Gitdeploy\\Helpers;\n\nclass Config {\n\n    public static funct"
  },
  {
    "path": "tools/src/Ftp.php",
    "chars": 5043,
    "preview": "<?php\n\nnamespace Brunodebarros\\Gitdeploy;\n\nuse Brunodebarros\\Gitdeploy\\Helpers;\n\nclass Ftp extends Server {\n\n    public "
  },
  {
    "path": "tools/src/Git.php",
    "chars": 4787,
    "preview": "<?php\n\nnamespace Brunodebarros\\Gitdeploy;\n\nuse Brunodebarros\\Gitdeploy\\Helpers;\n\nclass Git {\n\n    public static $git_exe"
  },
  {
    "path": "tools/src/Helpers.php",
    "chars": 1578,
    "preview": "<?php\n\nnamespace Brunodebarros\\Gitdeploy;\n\nclass Helpers {\n\n    public static function logmessage($message) {\n        st"
  },
  {
    "path": "tools/src/Server.php",
    "chars": 8254,
    "preview": "<?php\n\nnamespace Brunodebarros\\Gitdeploy;\n\nuse Brunodebarros\\Gitdeploy\\Helpers;\n\nabstract class Server {\n\n    public $co"
  },
  {
    "path": "tools/src/Sftp.php",
    "chars": 4450,
    "preview": "<?php\n\nnamespace Brunodebarros\\Gitdeploy;\n\nuse Brunodebarros\\Gitdeploy\\Helpers;\n\nclass Sftp extends Server {\n\n    public"
  }
]

// ... and 1 more files (download for full content)

About this extraction

This page contains the full source code of the BrunoDeBarros/git-deploy-php GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 14 files (40.7 KB), approximately 10.0k tokens, and a symbol index with 39 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!