xref: /dokuwiki/bin/gittool.php (revision 7e8500eea1e53b1de0e0f70400664afa442cd08d)
1c39ae2c9SAndreas Gohr#!/usr/bin/php
2c39ae2c9SAndreas Gohr<?php
3c39ae2c9SAndreas Gohrif(!defined('DOKU_INC')) define('DOKU_INC', realpath(dirname(__FILE__).'/../').'/');
4c39ae2c9SAndreas Gohrdefine('NOSESSION', 1);
5c39ae2c9SAndreas Gohrrequire_once(DOKU_INC.'inc/init.php');
6c39ae2c9SAndreas Gohr
7c39ae2c9SAndreas Gohr/**
8c39ae2c9SAndreas Gohr * Easily manage DokuWiki git repositories
9c39ae2c9SAndreas Gohr *
10c39ae2c9SAndreas Gohr * @author Andreas Gohr <andi@splitbrain.org>
11c39ae2c9SAndreas Gohr */
129fb66494SAndreas Gohrclass GitToolCLI extends DokuCLI {
13c39ae2c9SAndreas Gohr
149fb66494SAndreas Gohr    /**
159fb66494SAndreas Gohr     * Register options and arguments on the given $options object
169fb66494SAndreas Gohr     *
179fb66494SAndreas Gohr     * @param DokuCLI_Options $options
189fb66494SAndreas Gohr     * @return void
199fb66494SAndreas Gohr     */
209fb66494SAndreas Gohr    protected function setup(DokuCLI_Options $options) {
219fb66494SAndreas Gohr        $options->setHelp(
229fb66494SAndreas Gohr            "Manage git repositories for DokuWiki and its plugins and templates.\n\n".
239fb66494SAndreas Gohr            "$> ./bin/gittool.php clone gallery template:ach\n".
249fb66494SAndreas Gohr            "$> ./bin/gittool.php repos\n".
25ae1ce4a6SAndreas Gohr            "$> ./bin/gittool.php origin -v"
269fb66494SAndreas Gohr        );
27c39ae2c9SAndreas Gohr
289fb66494SAndreas Gohr        $options->registerArgument(
299fb66494SAndreas Gohr            'command',
309fb66494SAndreas Gohr            'Command to execute. See below',
319fb66494SAndreas Gohr            true
329fb66494SAndreas Gohr        );
33c39ae2c9SAndreas Gohr
349fb66494SAndreas Gohr        $options->registerCommand(
359fb66494SAndreas Gohr            'clone',
369fb66494SAndreas Gohr            'Tries to install a known plugin or template (prefix with template:) via git. Uses the DokuWiki.org '.
379fb66494SAndreas Gohr            'plugin repository to find the proper git repository. Multiple extensions can be given as parameters'
389fb66494SAndreas Gohr        );
399fb66494SAndreas Gohr        $options->registerArgument(
409fb66494SAndreas Gohr            'extension',
419fb66494SAndreas Gohr            'name of the extension to install, prefix with \'template:\' for templates',
429fb66494SAndreas Gohr            true,
439fb66494SAndreas Gohr            'clone'
449fb66494SAndreas Gohr        );
45c39ae2c9SAndreas Gohr
469fb66494SAndreas Gohr        $options->registerCommand(
479fb66494SAndreas Gohr            'install',
489fb66494SAndreas Gohr            'The same as clone, but when no git source repository can be found, the extension is installed via '.
499fb66494SAndreas Gohr            'download'
509fb66494SAndreas Gohr        );
519fb66494SAndreas Gohr        $options->registerArgument(
529fb66494SAndreas Gohr            'extension',
539fb66494SAndreas Gohr            'name of the extension to install, prefix with \'template:\' for templates',
549fb66494SAndreas Gohr            true,
559fb66494SAndreas Gohr            'install'
569fb66494SAndreas Gohr        );
57c39ae2c9SAndreas Gohr
589fb66494SAndreas Gohr        $options->registerCommand(
599fb66494SAndreas Gohr            'repos',
609fb66494SAndreas Gohr            'Lists all git repositories found in this DokuWiki installation'
619fb66494SAndreas Gohr        );
62c39ae2c9SAndreas Gohr
639fb66494SAndreas Gohr        $options->registerCommand(
649fb66494SAndreas Gohr            '*',
659fb66494SAndreas Gohr            'Any unknown commands are assumed to be arguments to git and will be executed in all repositories '.
669fb66494SAndreas Gohr            'found within this DokuWiki installation'
679fb66494SAndreas Gohr        );
68c39ae2c9SAndreas Gohr    }
69c39ae2c9SAndreas Gohr
70c39ae2c9SAndreas Gohr    /**
719fb66494SAndreas Gohr     * Your main program
729fb66494SAndreas Gohr     *
739fb66494SAndreas Gohr     * Arguments and options have been parsed when this is run
749fb66494SAndreas Gohr     *
759fb66494SAndreas Gohr     * @param DokuCLI_Options $options
769fb66494SAndreas Gohr     * @return void
779fb66494SAndreas Gohr     */
789fb66494SAndreas Gohr    protected function main(DokuCLI_Options $options) {
799fb66494SAndreas Gohr        $command = $options->getCmd();
809fb66494SAndreas Gohr        if(!$command) $command = array_shift($options->args);
819fb66494SAndreas Gohr
829fb66494SAndreas Gohr        switch($command) {
839fb66494SAndreas Gohr            case '':
841c36b3d8SAndreas Gohr                echo $options->help();
859fb66494SAndreas Gohr                break;
869fb66494SAndreas Gohr            case 'clone':
879fb66494SAndreas Gohr                $this->cmd_clone($options->args);
889fb66494SAndreas Gohr                break;
899fb66494SAndreas Gohr            case 'install':
909fb66494SAndreas Gohr                $this->cmd_install($options->args);
919fb66494SAndreas Gohr                break;
929fb66494SAndreas Gohr            case 'repo':
939fb66494SAndreas Gohr            case 'repos':
949fb66494SAndreas Gohr                $this->cmd_repos();
959fb66494SAndreas Gohr                break;
969fb66494SAndreas Gohr            default:
979fb66494SAndreas Gohr                $this->cmd_git($command, $options->args);
989fb66494SAndreas Gohr        }
999fb66494SAndreas Gohr    }
1009fb66494SAndreas Gohr
1019fb66494SAndreas Gohr    /**
102c39ae2c9SAndreas Gohr     * Tries to install the given extensions using git clone
103c39ae2c9SAndreas Gohr     *
10442ea7f44SGerrit Uitslag     * @param array      $extensions
105c39ae2c9SAndreas Gohr     */
106c39ae2c9SAndreas Gohr    public function cmd_clone($extensions) {
107c39ae2c9SAndreas Gohr        $errors    = array();
108c39ae2c9SAndreas Gohr        $succeeded = array();
109c39ae2c9SAndreas Gohr
110c39ae2c9SAndreas Gohr        foreach($extensions as $ext) {
111c39ae2c9SAndreas Gohr            $repo = $this->getSourceRepo($ext);
112c39ae2c9SAndreas Gohr
113c39ae2c9SAndreas Gohr            if(!$repo) {
1149fb66494SAndreas Gohr                $this->error("could not find a repository for $ext");
115c39ae2c9SAndreas Gohr                $errors[] = $ext;
116c39ae2c9SAndreas Gohr            } else {
117c39ae2c9SAndreas Gohr                if($this->cloneExtension($ext, $repo)) {
118c39ae2c9SAndreas Gohr                    $succeeded[] = $ext;
119c39ae2c9SAndreas Gohr                } else {
120c39ae2c9SAndreas Gohr                    $errors[] = $ext;
121c39ae2c9SAndreas Gohr                }
122c39ae2c9SAndreas Gohr            }
123c39ae2c9SAndreas Gohr        }
124c39ae2c9SAndreas Gohr
125c39ae2c9SAndreas Gohr        echo "\n";
1269fb66494SAndreas Gohr        if($succeeded) $this->success('successfully cloned the following extensions: '.join(', ', $succeeded));
1279fb66494SAndreas Gohr        if($errors) $this->error('failed to clone the following extensions: '.join(', ', $errors));
128c39ae2c9SAndreas Gohr    }
129c39ae2c9SAndreas Gohr
130c39ae2c9SAndreas Gohr    /**
131c39ae2c9SAndreas Gohr     * Tries to install the given extensions using git clone with fallback to install
132c39ae2c9SAndreas Gohr     *
13342ea7f44SGerrit Uitslag     * @param array      $extensions
134c39ae2c9SAndreas Gohr     */
135c39ae2c9SAndreas Gohr    public function cmd_install($extensions) {
136c39ae2c9SAndreas Gohr        $errors    = array();
137c39ae2c9SAndreas Gohr        $succeeded = array();
138c39ae2c9SAndreas Gohr
139c39ae2c9SAndreas Gohr        foreach($extensions as $ext) {
140c39ae2c9SAndreas Gohr            $repo = $this->getSourceRepo($ext);
141c39ae2c9SAndreas Gohr
142c39ae2c9SAndreas Gohr            if(!$repo) {
1439fb66494SAndreas Gohr                $this->info("could not find a repository for $ext");
144c39ae2c9SAndreas Gohr                if($this->downloadExtension($ext)) {
145c39ae2c9SAndreas Gohr                    $succeeded[] = $ext;
146c39ae2c9SAndreas Gohr                } else {
147c39ae2c9SAndreas Gohr                    $errors[] = $ext;
148c39ae2c9SAndreas Gohr                }
149c39ae2c9SAndreas Gohr            } else {
150c39ae2c9SAndreas Gohr                if($this->cloneExtension($ext, $repo)) {
151c39ae2c9SAndreas Gohr                    $succeeded[] = $ext;
152c39ae2c9SAndreas Gohr                } else {
153c39ae2c9SAndreas Gohr                    $errors[] = $ext;
154c39ae2c9SAndreas Gohr                }
155c39ae2c9SAndreas Gohr            }
156c39ae2c9SAndreas Gohr        }
157c39ae2c9SAndreas Gohr
158c39ae2c9SAndreas Gohr        echo "\n";
1599fb66494SAndreas Gohr        if($succeeded) $this->success('successfully installed the following extensions: '.join(', ', $succeeded));
1609fb66494SAndreas Gohr        if($errors) $this->error('failed to install the following extensions: '.join(', ', $errors));
161c39ae2c9SAndreas Gohr    }
162c39ae2c9SAndreas Gohr
163c39ae2c9SAndreas Gohr    /**
164c39ae2c9SAndreas Gohr     * Executes the given git command in every repository
165c39ae2c9SAndreas Gohr     *
166c39ae2c9SAndreas Gohr     * @param $cmd
167c39ae2c9SAndreas Gohr     * @param $arg
168c39ae2c9SAndreas Gohr     */
169c39ae2c9SAndreas Gohr    public function cmd_git($cmd, $arg) {
170c39ae2c9SAndreas Gohr        $repos = $this->findRepos();
171c39ae2c9SAndreas Gohr
172c39ae2c9SAndreas Gohr        $shell = array_merge(array('git', $cmd), $arg);
173c39ae2c9SAndreas Gohr        $shell = array_map('escapeshellarg', $shell);
174c39ae2c9SAndreas Gohr        $shell = join(' ', $shell);
175c39ae2c9SAndreas Gohr
176c39ae2c9SAndreas Gohr        foreach($repos as $repo) {
177c39ae2c9SAndreas Gohr            if(!@chdir($repo)) {
1789fb66494SAndreas Gohr                $this->error("Could not change into $repo");
179c39ae2c9SAndreas Gohr                continue;
180c39ae2c9SAndreas Gohr            }
181c39ae2c9SAndreas Gohr
182c39ae2c9SAndreas Gohr            echo "\n";
1839fb66494SAndreas Gohr            $this->info("executing $shell in $repo");
184c39ae2c9SAndreas Gohr            $ret = 0;
185c39ae2c9SAndreas Gohr            system($shell, $ret);
186c39ae2c9SAndreas Gohr
187c39ae2c9SAndreas Gohr            if($ret == 0) {
1889fb66494SAndreas Gohr                $this->success("git succeeded in $repo");
189c39ae2c9SAndreas Gohr            } else {
1909fb66494SAndreas Gohr                $this->error("git failed in $repo");
191c39ae2c9SAndreas Gohr            }
192c39ae2c9SAndreas Gohr        }
193c39ae2c9SAndreas Gohr    }
194c39ae2c9SAndreas Gohr
195c39ae2c9SAndreas Gohr    /**
196c39ae2c9SAndreas Gohr     * Simply lists the repositories
197c39ae2c9SAndreas Gohr     */
198c39ae2c9SAndreas Gohr    public function cmd_repos() {
199c39ae2c9SAndreas Gohr        $repos = $this->findRepos();
200c39ae2c9SAndreas Gohr        foreach($repos as $repo) {
201c39ae2c9SAndreas Gohr            echo "$repo\n";
202c39ae2c9SAndreas Gohr        }
203c39ae2c9SAndreas Gohr    }
204c39ae2c9SAndreas Gohr
205c39ae2c9SAndreas Gohr    /**
206c39ae2c9SAndreas Gohr     * Install extension from the given download URL
207c39ae2c9SAndreas Gohr     *
208c39ae2c9SAndreas Gohr     * @param string $ext
209*7e8500eeSGerrit Uitslag     * @return bool|null
210c39ae2c9SAndreas Gohr     */
211c39ae2c9SAndreas Gohr    private function downloadExtension($ext) {
212c39ae2c9SAndreas Gohr        /** @var helper_plugin_extension_extension $plugin */
213c39ae2c9SAndreas Gohr        $plugin = plugin_load('helper', 'extension_extension');
214c39ae2c9SAndreas Gohr        if(!$ext) die("extension plugin not available, can't continue");
215*7e8500eeSGerrit Uitslag
216c39ae2c9SAndreas Gohr        $plugin->setExtension($ext);
217c39ae2c9SAndreas Gohr
218c39ae2c9SAndreas Gohr        $url = $plugin->getDownloadURL();
219c39ae2c9SAndreas Gohr        if(!$url) {
2209fb66494SAndreas Gohr            $this->error("no download URL for $ext");
221c39ae2c9SAndreas Gohr            return false;
222c39ae2c9SAndreas Gohr        }
223c39ae2c9SAndreas Gohr
224c39ae2c9SAndreas Gohr        $ok = false;
225c39ae2c9SAndreas Gohr        try {
2269fb66494SAndreas Gohr            $this->info("installing $ext via download from $url");
227c39ae2c9SAndreas Gohr            $ok = $plugin->installFromURL($url);
228c39ae2c9SAndreas Gohr        } catch(Exception $e) {
2299fb66494SAndreas Gohr            $this->error($e->getMessage());
230c39ae2c9SAndreas Gohr        }
231c39ae2c9SAndreas Gohr
232c39ae2c9SAndreas Gohr        if($ok) {
2339fb66494SAndreas Gohr            $this->success("installed $ext via download");
234c39ae2c9SAndreas Gohr            return true;
235c39ae2c9SAndreas Gohr        } else {
2369fb66494SAndreas Gohr            $this->success("failed to install $ext via download");
237c39ae2c9SAndreas Gohr            return false;
238c39ae2c9SAndreas Gohr        }
239c39ae2c9SAndreas Gohr    }
240c39ae2c9SAndreas Gohr
241c39ae2c9SAndreas Gohr    /**
242c39ae2c9SAndreas Gohr     * Clones the extension from the given repository
243c39ae2c9SAndreas Gohr     *
244c39ae2c9SAndreas Gohr     * @param string $ext
245c39ae2c9SAndreas Gohr     * @param string $repo
246c39ae2c9SAndreas Gohr     * @return bool
247c39ae2c9SAndreas Gohr     */
248c39ae2c9SAndreas Gohr    private function cloneExtension($ext, $repo) {
249c39ae2c9SAndreas Gohr        if(substr($ext, 0, 9) == 'template:') {
250c39ae2c9SAndreas Gohr            $target = fullpath(tpl_incdir().'../'.substr($ext, 9));
251c39ae2c9SAndreas Gohr        } else {
252c39ae2c9SAndreas Gohr            $target = DOKU_PLUGIN.$ext;
253c39ae2c9SAndreas Gohr        }
254c39ae2c9SAndreas Gohr
2559fb66494SAndreas Gohr        $this->info("cloning $ext from $repo to $target");
256c39ae2c9SAndreas Gohr        $ret = 0;
257c39ae2c9SAndreas Gohr        system("git clone $repo $target", $ret);
258c39ae2c9SAndreas Gohr        if($ret === 0) {
2599fb66494SAndreas Gohr            $this->success("cloning of $ext succeeded");
260c39ae2c9SAndreas Gohr            return true;
261c39ae2c9SAndreas Gohr        } else {
2629fb66494SAndreas Gohr            $this->error("cloning of $ext failed");
263c39ae2c9SAndreas Gohr            return false;
264c39ae2c9SAndreas Gohr        }
265c39ae2c9SAndreas Gohr    }
266c39ae2c9SAndreas Gohr
267c39ae2c9SAndreas Gohr    /**
268c39ae2c9SAndreas Gohr     * Returns all git repositories in this DokuWiki install
269c39ae2c9SAndreas Gohr     *
270c39ae2c9SAndreas Gohr     * Looks in root, template and plugin directories only.
271c39ae2c9SAndreas Gohr     *
272c39ae2c9SAndreas Gohr     * @return array
273c39ae2c9SAndreas Gohr     */
274c39ae2c9SAndreas Gohr    private function findRepos() {
2759fb66494SAndreas Gohr        $this->info('Looking for .git directories');
276c39ae2c9SAndreas Gohr        $data = array_merge(
277c39ae2c9SAndreas Gohr            glob(DOKU_INC.'.git', GLOB_ONLYDIR),
278c39ae2c9SAndreas Gohr            glob(DOKU_PLUGIN.'*/.git', GLOB_ONLYDIR),
279c39ae2c9SAndreas Gohr            glob(fullpath(tpl_incdir().'../').'/*/.git', GLOB_ONLYDIR)
280c39ae2c9SAndreas Gohr        );
281c39ae2c9SAndreas Gohr
282c39ae2c9SAndreas Gohr        if(!$data) {
2839fb66494SAndreas Gohr            $this->error('Found no .git directories');
284c39ae2c9SAndreas Gohr        } else {
2859fb66494SAndreas Gohr            $this->success('Found '.count($data).' .git directories');
286c39ae2c9SAndreas Gohr        }
287b0c7084fSAndreas Gohr        $data = array_map('fullpath', array_map('dirname', $data));
288c39ae2c9SAndreas Gohr        return $data;
289c39ae2c9SAndreas Gohr    }
290c39ae2c9SAndreas Gohr
291c39ae2c9SAndreas Gohr    /**
292c39ae2c9SAndreas Gohr     * Returns the repository for the given extension
293c39ae2c9SAndreas Gohr     *
294c39ae2c9SAndreas Gohr     * @param $extension
29542ea7f44SGerrit Uitslag     * @return false|string
296c39ae2c9SAndreas Gohr     */
297c39ae2c9SAndreas Gohr    private function getSourceRepo($extension) {
298c39ae2c9SAndreas Gohr        /** @var helper_plugin_extension_extension $ext */
299c39ae2c9SAndreas Gohr        $ext = plugin_load('helper', 'extension_extension');
300c39ae2c9SAndreas Gohr        if(!$ext) die("extension plugin not available, can't continue");
301*7e8500eeSGerrit Uitslag
302c39ae2c9SAndreas Gohr        $ext->setExtension($extension);
303c39ae2c9SAndreas Gohr
30468cf024bSAndreas Gohr        $repourl = $ext->getSourcerepoURL();
30568cf024bSAndreas Gohr        if(!$repourl) return false;
30668cf024bSAndreas Gohr
307c39ae2c9SAndreas Gohr        // match github repos
30868cf024bSAndreas Gohr        if(preg_match('/github\.com\/([^\/]+)\/([^\/]+)/i', $repourl, $m)) {
309c39ae2c9SAndreas Gohr            $user = $m[1];
310c39ae2c9SAndreas Gohr            $repo = $m[2];
311c39ae2c9SAndreas Gohr            return 'https://github.com/'.$user.'/'.$repo.'.git';
312c39ae2c9SAndreas Gohr        }
313c39ae2c9SAndreas Gohr
314c39ae2c9SAndreas Gohr        // match gitorious repos
31568cf024bSAndreas Gohr        if(preg_match('/gitorious.org\/([^\/]+)\/([^\/]+)?/i', $repourl, $m)) {
316c39ae2c9SAndreas Gohr            $user = $m[1];
317c39ae2c9SAndreas Gohr            $repo = $m[2];
318c39ae2c9SAndreas Gohr            if(!$repo) $repo = $user;
319c39ae2c9SAndreas Gohr
320c39ae2c9SAndreas Gohr            return 'https://git.gitorious.org/'.$user.'/'.$repo.'.git';
321c39ae2c9SAndreas Gohr        }
322c39ae2c9SAndreas Gohr
323c39ae2c9SAndreas Gohr        // match bitbucket repos - most people seem to use mercurial there though
32468cf024bSAndreas Gohr        if(preg_match('/bitbucket\.org\/([^\/]+)\/([^\/]+)/i', $repourl, $m)) {
325c39ae2c9SAndreas Gohr            $user = $m[1];
326c39ae2c9SAndreas Gohr            $repo = $m[2];
327c39ae2c9SAndreas Gohr            return 'https://bitbucket.org/'.$user.'/'.$repo.'.git';
328c39ae2c9SAndreas Gohr        }
329c39ae2c9SAndreas Gohr
330c39ae2c9SAndreas Gohr        return false;
331c39ae2c9SAndreas Gohr    }
332c39ae2c9SAndreas Gohr}
333c39ae2c9SAndreas Gohr
334b0b7909bSAndreas Gohr// Main
335b0b7909bSAndreas Gohr$cli = new GitToolCLI();
336b0b7909bSAndreas Gohr$cli->run();