xref: /dokuwiki/bin/gittool.php (revision d868eb89f182718a31113373a6272670bd7f8012)
1cbfa4829SPhy#!/usr/bin/env php
2c39ae2c9SAndreas Gohr<?php
3cbeaa4a0SAndreas Gohr
4cbeaa4a0SAndreas Gohruse splitbrain\phpcli\CLI;
5cbeaa4a0SAndreas Gohruse splitbrain\phpcli\Options;
6cbeaa4a0SAndreas Gohr
7b1f206e1SAndreas Gohrif(!defined('DOKU_INC')) define('DOKU_INC', realpath(__DIR__ . '/../') . '/');
8c39ae2c9SAndreas Gohrdefine('NOSESSION', 1);
9c39ae2c9SAndreas Gohrrequire_once(DOKU_INC . 'inc/init.php');
10c39ae2c9SAndreas Gohr
11c39ae2c9SAndreas Gohr/**
12c39ae2c9SAndreas Gohr * Easily manage DokuWiki git repositories
13c39ae2c9SAndreas Gohr *
14c39ae2c9SAndreas Gohr * @author Andreas Gohr <andi@splitbrain.org>
15c39ae2c9SAndreas Gohr */
168c7c53b0SAndreas Gohrclass GitToolCLI extends CLI
178c7c53b0SAndreas Gohr{
18c39ae2c9SAndreas Gohr
199fb66494SAndreas Gohr    /**
209fb66494SAndreas Gohr     * Register options and arguments on the given $options object
219fb66494SAndreas Gohr     *
22cbeaa4a0SAndreas Gohr     * @param Options $options
239fb66494SAndreas Gohr     * @return void
249fb66494SAndreas Gohr     */
25*d868eb89SAndreas Gohr    protected function setup(Options $options)
26*d868eb89SAndreas Gohr    {
279fb66494SAndreas Gohr        $options->setHelp(
289fb66494SAndreas Gohr            "Manage git repositories for DokuWiki and its plugins and templates.\n\n" .
299fb66494SAndreas Gohr            "$> ./bin/gittool.php clone gallery template:ach\n" .
309fb66494SAndreas Gohr            "$> ./bin/gittool.php repos\n" .
31ae1ce4a6SAndreas Gohr            "$> ./bin/gittool.php origin -v"
329fb66494SAndreas Gohr        );
33c39ae2c9SAndreas Gohr
349fb66494SAndreas Gohr        $options->registerArgument(
359fb66494SAndreas Gohr            'command',
369fb66494SAndreas Gohr            'Command to execute. See below',
379fb66494SAndreas Gohr            true
389fb66494SAndreas Gohr        );
39c39ae2c9SAndreas Gohr
409fb66494SAndreas Gohr        $options->registerCommand(
419fb66494SAndreas Gohr            'clone',
429fb66494SAndreas Gohr            'Tries to install a known plugin or template (prefix with template:) via git. Uses the DokuWiki.org ' .
439fb66494SAndreas Gohr            'plugin repository to find the proper git repository. Multiple extensions can be given as parameters'
449fb66494SAndreas Gohr        );
459fb66494SAndreas Gohr        $options->registerArgument(
469fb66494SAndreas Gohr            'extension',
479fb66494SAndreas Gohr            'name of the extension to install, prefix with \'template:\' for templates',
489fb66494SAndreas Gohr            true,
499fb66494SAndreas Gohr            'clone'
509fb66494SAndreas Gohr        );
51c39ae2c9SAndreas Gohr
529fb66494SAndreas Gohr        $options->registerCommand(
539fb66494SAndreas Gohr            'install',
549fb66494SAndreas Gohr            'The same as clone, but when no git source repository can be found, the extension is installed via ' .
559fb66494SAndreas Gohr            'download'
569fb66494SAndreas Gohr        );
579fb66494SAndreas Gohr        $options->registerArgument(
589fb66494SAndreas Gohr            'extension',
599fb66494SAndreas Gohr            'name of the extension to install, prefix with \'template:\' for templates',
609fb66494SAndreas Gohr            true,
619fb66494SAndreas Gohr            'install'
629fb66494SAndreas Gohr        );
63c39ae2c9SAndreas Gohr
649fb66494SAndreas Gohr        $options->registerCommand(
659fb66494SAndreas Gohr            'repos',
669fb66494SAndreas Gohr            'Lists all git repositories found in this DokuWiki installation'
679fb66494SAndreas Gohr        );
68c39ae2c9SAndreas Gohr
699fb66494SAndreas Gohr        $options->registerCommand(
709fb66494SAndreas Gohr            '*',
719fb66494SAndreas Gohr            'Any unknown commands are assumed to be arguments to git and will be executed in all repositories ' .
729fb66494SAndreas Gohr            'found within this DokuWiki installation'
739fb66494SAndreas Gohr        );
74c39ae2c9SAndreas Gohr    }
75c39ae2c9SAndreas Gohr
76c39ae2c9SAndreas Gohr    /**
779fb66494SAndreas Gohr     * Your main program
789fb66494SAndreas Gohr     *
799fb66494SAndreas Gohr     * Arguments and options have been parsed when this is run
809fb66494SAndreas Gohr     *
81cbeaa4a0SAndreas Gohr     * @param Options $options
829fb66494SAndreas Gohr     * @return void
839fb66494SAndreas Gohr     */
84*d868eb89SAndreas Gohr    protected function main(Options $options)
85*d868eb89SAndreas Gohr    {
869fb66494SAndreas Gohr        $command = $options->getCmd();
87cbeaa4a0SAndreas Gohr        $args = $options->getArgs();
88cbeaa4a0SAndreas Gohr        if(!$command) $command = array_shift($args);
899fb66494SAndreas Gohr
909fb66494SAndreas Gohr        switch($command) {
919fb66494SAndreas Gohr            case '':
921c36b3d8SAndreas Gohr                echo $options->help();
939fb66494SAndreas Gohr                break;
949fb66494SAndreas Gohr            case 'clone':
952b2d0ba9SAndreas Gohr                $this->cmdClone($args);
969fb66494SAndreas Gohr                break;
979fb66494SAndreas Gohr            case 'install':
982b2d0ba9SAndreas Gohr                $this->cmdInstall($args);
999fb66494SAndreas Gohr                break;
1009fb66494SAndreas Gohr            case 'repo':
1019fb66494SAndreas Gohr            case 'repos':
1022b2d0ba9SAndreas Gohr                $this->cmdRepos();
1039fb66494SAndreas Gohr                break;
1049fb66494SAndreas Gohr            default:
1052b2d0ba9SAndreas Gohr                $this->cmdGit($command, $args);
1069fb66494SAndreas Gohr        }
1079fb66494SAndreas Gohr    }
1089fb66494SAndreas Gohr
1099fb66494SAndreas Gohr    /**
110c39ae2c9SAndreas Gohr     * Tries to install the given extensions using git clone
111c39ae2c9SAndreas Gohr     *
11242ea7f44SGerrit Uitslag     * @param array $extensions
113c39ae2c9SAndreas Gohr     */
114*d868eb89SAndreas Gohr    public function cmdClone($extensions)
115*d868eb89SAndreas Gohr    {
116b1f206e1SAndreas Gohr        $errors = [];
117b1f206e1SAndreas Gohr        $succeeded = [];
118c39ae2c9SAndreas Gohr
119c39ae2c9SAndreas Gohr        foreach($extensions as $ext) {
120c39ae2c9SAndreas Gohr            $repo = $this->getSourceRepo($ext);
121c39ae2c9SAndreas Gohr
122c39ae2c9SAndreas Gohr            if (!$repo) {
1239fb66494SAndreas Gohr                $this->error("could not find a repository for $ext");
124c39ae2c9SAndreas Gohr                $errors[] = $ext;
125b1f206e1SAndreas Gohr            } elseif ($this->cloneExtension($ext, $repo)) {
126c39ae2c9SAndreas Gohr                $succeeded[] = $ext;
127c39ae2c9SAndreas Gohr            } else {
128c39ae2c9SAndreas Gohr                $errors[] = $ext;
129c39ae2c9SAndreas Gohr            }
130c39ae2c9SAndreas Gohr        }
131c39ae2c9SAndreas Gohr
132c39ae2c9SAndreas Gohr        echo "\n";
133b1f206e1SAndreas Gohr        if($succeeded) $this->success('successfully cloned the following extensions: ' . implode(', ', $succeeded));
134b1f206e1SAndreas Gohr        if($errors) $this->error('failed to clone the following extensions: ' . implode(', ', $errors));
135c39ae2c9SAndreas Gohr    }
136c39ae2c9SAndreas Gohr
137c39ae2c9SAndreas Gohr    /**
138c39ae2c9SAndreas Gohr     * Tries to install the given extensions using git clone with fallback to install
139c39ae2c9SAndreas Gohr     *
14042ea7f44SGerrit Uitslag     * @param array $extensions
141c39ae2c9SAndreas Gohr     */
142*d868eb89SAndreas Gohr    public function cmdInstall($extensions)
143*d868eb89SAndreas Gohr    {
144b1f206e1SAndreas Gohr        $errors = [];
145b1f206e1SAndreas Gohr        $succeeded = [];
146c39ae2c9SAndreas Gohr
147c39ae2c9SAndreas Gohr        foreach($extensions as $ext) {
148c39ae2c9SAndreas Gohr            $repo = $this->getSourceRepo($ext);
149c39ae2c9SAndreas Gohr
150c39ae2c9SAndreas Gohr            if (!$repo) {
1519fb66494SAndreas Gohr                $this->info("could not find a repository for $ext");
152c39ae2c9SAndreas Gohr                if($this->downloadExtension($ext)) {
153c39ae2c9SAndreas Gohr                    $succeeded[] = $ext;
154c39ae2c9SAndreas Gohr                } else {
155c39ae2c9SAndreas Gohr                    $errors[] = $ext;
156c39ae2c9SAndreas Gohr                }
157b1f206e1SAndreas Gohr            } elseif ($this->cloneExtension($ext, $repo)) {
158c39ae2c9SAndreas Gohr                $succeeded[] = $ext;
159c39ae2c9SAndreas Gohr            } else {
160c39ae2c9SAndreas Gohr                $errors[] = $ext;
161c39ae2c9SAndreas Gohr            }
162c39ae2c9SAndreas Gohr        }
163c39ae2c9SAndreas Gohr
164c39ae2c9SAndreas Gohr        echo "\n";
165b1f206e1SAndreas Gohr        if($succeeded) $this->success('successfully installed the following extensions: ' . implode(', ', $succeeded));
166b1f206e1SAndreas Gohr        if($errors) $this->error('failed to install the following extensions: ' . implode(', ', $errors));
167c39ae2c9SAndreas Gohr    }
168c39ae2c9SAndreas Gohr
169c39ae2c9SAndreas Gohr    /**
170c39ae2c9SAndreas Gohr     * Executes the given git command in every repository
171c39ae2c9SAndreas Gohr     *
172c39ae2c9SAndreas Gohr     * @param $cmd
173c39ae2c9SAndreas Gohr     * @param $arg
174c39ae2c9SAndreas Gohr     */
175*d868eb89SAndreas Gohr    public function cmdGit($cmd, $arg)
176*d868eb89SAndreas Gohr    {
177c39ae2c9SAndreas Gohr        $repos = $this->findRepos();
178c39ae2c9SAndreas Gohr
179b1f206e1SAndreas Gohr        $shell = array_merge(['git', $cmd], $arg);
180c39ae2c9SAndreas Gohr        $shell = array_map('escapeshellarg', $shell);
181b1f206e1SAndreas Gohr        $shell = implode(' ', $shell);
182c39ae2c9SAndreas Gohr
183c39ae2c9SAndreas Gohr        foreach($repos as $repo) {
184c39ae2c9SAndreas Gohr            if(!@chdir($repo)) {
1859fb66494SAndreas Gohr                $this->error("Could not change into $repo");
186c39ae2c9SAndreas Gohr                continue;
187c39ae2c9SAndreas Gohr            }
188c39ae2c9SAndreas Gohr
1899fb66494SAndreas Gohr            $this->info("executing $shell in $repo");
190c39ae2c9SAndreas Gohr            $ret = 0;
191c39ae2c9SAndreas Gohr            system($shell, $ret);
192c39ae2c9SAndreas Gohr
193c39ae2c9SAndreas Gohr            if($ret == 0) {
1949fb66494SAndreas Gohr                $this->success("git succeeded in $repo");
195c39ae2c9SAndreas Gohr            } else {
1969fb66494SAndreas Gohr                $this->error("git failed in $repo");
197c39ae2c9SAndreas Gohr            }
198c39ae2c9SAndreas Gohr        }
199c39ae2c9SAndreas Gohr    }
200c39ae2c9SAndreas Gohr
201c39ae2c9SAndreas Gohr    /**
202c39ae2c9SAndreas Gohr     * Simply lists the repositories
203c39ae2c9SAndreas Gohr     */
204*d868eb89SAndreas Gohr    public function cmdRepos()
205*d868eb89SAndreas Gohr    {
206c39ae2c9SAndreas Gohr        $repos = $this->findRepos();
207c39ae2c9SAndreas Gohr        foreach($repos as $repo) {
208c39ae2c9SAndreas Gohr            echo "$repo\n";
209c39ae2c9SAndreas Gohr        }
210c39ae2c9SAndreas Gohr    }
211c39ae2c9SAndreas Gohr
212c39ae2c9SAndreas Gohr    /**
213c39ae2c9SAndreas Gohr     * Install extension from the given download URL
214c39ae2c9SAndreas Gohr     *
215c39ae2c9SAndreas Gohr     * @param string $ext
2167e8500eeSGerrit Uitslag     * @return bool|null
217c39ae2c9SAndreas Gohr     */
218*d868eb89SAndreas Gohr    private function downloadExtension($ext)
219*d868eb89SAndreas Gohr    {
220c39ae2c9SAndreas Gohr        /** @var helper_plugin_extension_extension $plugin */
221c39ae2c9SAndreas Gohr        $plugin = plugin_load('helper', 'extension_extension');
222c39ae2c9SAndreas Gohr        if(!$ext) die("extension plugin not available, can't continue");
2237e8500eeSGerrit Uitslag
224c39ae2c9SAndreas Gohr        $plugin->setExtension($ext);
225c39ae2c9SAndreas Gohr
226c39ae2c9SAndreas Gohr        $url = $plugin->getDownloadURL();
227c39ae2c9SAndreas Gohr        if(!$url) {
2289fb66494SAndreas Gohr            $this->error("no download URL for $ext");
229c39ae2c9SAndreas Gohr            return false;
230c39ae2c9SAndreas Gohr        }
231c39ae2c9SAndreas Gohr
232c39ae2c9SAndreas Gohr        $ok = false;
233c39ae2c9SAndreas Gohr        try {
2349fb66494SAndreas Gohr            $this->info("installing $ext via download from $url");
235c39ae2c9SAndreas Gohr            $ok = $plugin->installFromURL($url);
236c39ae2c9SAndreas Gohr        } catch(Exception $e) {
2379fb66494SAndreas Gohr            $this->error($e->getMessage());
238c39ae2c9SAndreas Gohr        }
239c39ae2c9SAndreas Gohr
240c39ae2c9SAndreas Gohr        if($ok) {
2419fb66494SAndreas Gohr            $this->success("installed $ext via download");
242c39ae2c9SAndreas Gohr            return true;
243c39ae2c9SAndreas Gohr        } else {
2449fb66494SAndreas Gohr            $this->success("failed to install $ext via download");
245c39ae2c9SAndreas Gohr            return false;
246c39ae2c9SAndreas Gohr        }
247c39ae2c9SAndreas Gohr    }
248c39ae2c9SAndreas Gohr
249c39ae2c9SAndreas Gohr    /**
250c39ae2c9SAndreas Gohr     * Clones the extension from the given repository
251c39ae2c9SAndreas Gohr     *
252c39ae2c9SAndreas Gohr     * @param string $ext
253c39ae2c9SAndreas Gohr     * @param string $repo
254c39ae2c9SAndreas Gohr     * @return bool
255c39ae2c9SAndreas Gohr     */
256*d868eb89SAndreas Gohr    private function cloneExtension($ext, $repo)
257*d868eb89SAndreas Gohr    {
258c39ae2c9SAndreas Gohr        if(substr($ext, 0, 9) == 'template:') {
259c39ae2c9SAndreas Gohr            $target = fullpath(tpl_incdir() . '../' . substr($ext, 9));
260c39ae2c9SAndreas Gohr        } else {
261c39ae2c9SAndreas Gohr            $target = DOKU_PLUGIN . $ext;
262c39ae2c9SAndreas Gohr        }
263c39ae2c9SAndreas Gohr
2649fb66494SAndreas Gohr        $this->info("cloning $ext from $repo to $target");
265c39ae2c9SAndreas Gohr        $ret = 0;
266c39ae2c9SAndreas Gohr        system("git clone $repo $target", $ret);
267c39ae2c9SAndreas Gohr        if($ret === 0) {
2689fb66494SAndreas Gohr            $this->success("cloning of $ext succeeded");
269c39ae2c9SAndreas Gohr            return true;
270c39ae2c9SAndreas Gohr        } else {
2719fb66494SAndreas Gohr            $this->error("cloning of $ext failed");
272c39ae2c9SAndreas Gohr            return false;
273c39ae2c9SAndreas Gohr        }
274c39ae2c9SAndreas Gohr    }
275c39ae2c9SAndreas Gohr
276c39ae2c9SAndreas Gohr    /**
277c39ae2c9SAndreas Gohr     * Returns all git repositories in this DokuWiki install
278c39ae2c9SAndreas Gohr     *
279c39ae2c9SAndreas Gohr     * Looks in root, template and plugin directories only.
280c39ae2c9SAndreas Gohr     *
281c39ae2c9SAndreas Gohr     * @return array
282c39ae2c9SAndreas Gohr     */
283*d868eb89SAndreas Gohr    private function findRepos()
284*d868eb89SAndreas Gohr    {
2859fb66494SAndreas Gohr        $this->info('Looking for .git directories');
286c39ae2c9SAndreas Gohr        $data = array_merge(
287c39ae2c9SAndreas Gohr            glob(DOKU_INC . '.git', GLOB_ONLYDIR),
288c39ae2c9SAndreas Gohr            glob(DOKU_PLUGIN . '*/.git', GLOB_ONLYDIR),
289c39ae2c9SAndreas Gohr            glob(fullpath(tpl_incdir() . '../') . '/*/.git', GLOB_ONLYDIR)
290c39ae2c9SAndreas Gohr        );
291c39ae2c9SAndreas Gohr
292c39ae2c9SAndreas Gohr        if(!$data) {
2939fb66494SAndreas Gohr            $this->error('Found no .git directories');
294c39ae2c9SAndreas Gohr        } else {
2959fb66494SAndreas Gohr            $this->success('Found ' . count($data) . ' .git directories');
296c39ae2c9SAndreas Gohr        }
297b0c7084fSAndreas Gohr        $data = array_map('fullpath', array_map('dirname', $data));
298c39ae2c9SAndreas Gohr        return $data;
299c39ae2c9SAndreas Gohr    }
300c39ae2c9SAndreas Gohr
301c39ae2c9SAndreas Gohr    /**
302c39ae2c9SAndreas Gohr     * Returns the repository for the given extension
303c39ae2c9SAndreas Gohr     *
304c39ae2c9SAndreas Gohr     * @param $extension
30542ea7f44SGerrit Uitslag     * @return false|string
306c39ae2c9SAndreas Gohr     */
307*d868eb89SAndreas Gohr    private function getSourceRepo($extension)
308*d868eb89SAndreas Gohr    {
309c39ae2c9SAndreas Gohr        /** @var helper_plugin_extension_extension $ext */
310c39ae2c9SAndreas Gohr        $ext = plugin_load('helper', 'extension_extension');
311c39ae2c9SAndreas Gohr        if(!$ext) die("extension plugin not available, can't continue");
3127e8500eeSGerrit Uitslag
313c39ae2c9SAndreas Gohr        $ext->setExtension($extension);
314c39ae2c9SAndreas Gohr
31568cf024bSAndreas Gohr        $repourl = $ext->getSourcerepoURL();
31668cf024bSAndreas Gohr        if(!$repourl) return false;
31768cf024bSAndreas Gohr
318c39ae2c9SAndreas Gohr        // match github repos
31968cf024bSAndreas Gohr        if(preg_match('/github\.com\/([^\/]+)\/([^\/]+)/i', $repourl, $m)) {
320c39ae2c9SAndreas Gohr            $user = $m[1];
321c39ae2c9SAndreas Gohr            $repo = $m[2];
322c39ae2c9SAndreas Gohr            return 'https://github.com/' . $user . '/' . $repo . '.git';
323c39ae2c9SAndreas Gohr        }
324c39ae2c9SAndreas Gohr
325c39ae2c9SAndreas Gohr        // match gitorious repos
32668cf024bSAndreas Gohr        if(preg_match('/gitorious.org\/([^\/]+)\/([^\/]+)?/i', $repourl, $m)) {
327c39ae2c9SAndreas Gohr            $user = $m[1];
328c39ae2c9SAndreas Gohr            $repo = $m[2];
329c39ae2c9SAndreas Gohr            if(!$repo) $repo = $user;
330c39ae2c9SAndreas Gohr
331c39ae2c9SAndreas Gohr            return 'https://git.gitorious.org/' . $user . '/' . $repo . '.git';
332c39ae2c9SAndreas Gohr        }
333c39ae2c9SAndreas Gohr
334c39ae2c9SAndreas Gohr        // match bitbucket repos - most people seem to use mercurial there though
33568cf024bSAndreas Gohr        if(preg_match('/bitbucket\.org\/([^\/]+)\/([^\/]+)/i', $repourl, $m)) {
336c39ae2c9SAndreas Gohr            $user = $m[1];
337c39ae2c9SAndreas Gohr            $repo = $m[2];
338c39ae2c9SAndreas Gohr            return 'https://bitbucket.org/' . $user . '/' . $repo . '.git';
339c39ae2c9SAndreas Gohr        }
340c39ae2c9SAndreas Gohr
341c39ae2c9SAndreas Gohr        return false;
342c39ae2c9SAndreas Gohr    }
343c39ae2c9SAndreas Gohr}
344c39ae2c9SAndreas Gohr
345b0b7909bSAndreas Gohr// Main
346b0b7909bSAndreas Gohr$cli = new GitToolCLI();
347b0b7909bSAndreas Gohr$cli->run();
348