xref: /dokuwiki/bin/gittool.php (revision 1a7e82cdf101b66c3bfaaf013366ce71cbb57044)
1cbfa4829SPhy#!/usr/bin/env php
2c39ae2c9SAndreas Gohr<?php
3cbeaa4a0SAndreas Gohr
4*1a7e82cdSAnna Dabrowskause \dokuwiki\plugin\extension\Extension;
5*1a7e82cdSAnna Dabrowskause dokuwiki\plugin\extension\Installer;
6cbeaa4a0SAndreas Gohruse splitbrain\phpcli\CLI;
7cbeaa4a0SAndreas Gohruse splitbrain\phpcli\Options;
8cbeaa4a0SAndreas Gohr
9b1f206e1SAndreas Gohrif (!defined('DOKU_INC')) define('DOKU_INC', realpath(__DIR__ . '/../') . '/');
10c39ae2c9SAndreas Gohrdefine('NOSESSION', 1);
11c39ae2c9SAndreas Gohrrequire_once(DOKU_INC . 'inc/init.php');
12c39ae2c9SAndreas Gohr
13c39ae2c9SAndreas Gohr/**
14c39ae2c9SAndreas Gohr * Easily manage DokuWiki git repositories
15c39ae2c9SAndreas Gohr *
16c39ae2c9SAndreas Gohr * @author Andreas Gohr <andi@splitbrain.org>
17c39ae2c9SAndreas Gohr */
188c7c53b0SAndreas Gohrclass GitToolCLI extends CLI
198c7c53b0SAndreas Gohr{
209fb66494SAndreas Gohr    /**
219fb66494SAndreas Gohr     * Register options and arguments on the given $options object
229fb66494SAndreas Gohr     *
23cbeaa4a0SAndreas Gohr     * @param Options $options
249fb66494SAndreas Gohr     * @return void
259fb66494SAndreas Gohr     */
26d868eb89SAndreas Gohr    protected function setup(Options $options)
27d868eb89SAndreas Gohr    {
289fb66494SAndreas Gohr        $options->setHelp(
299fb66494SAndreas Gohr            "Manage git repositories for DokuWiki and its plugins and templates.\n\n" .
309fb66494SAndreas Gohr            "$> ./bin/gittool.php clone gallery template:ach\n" .
319fb66494SAndreas Gohr            "$> ./bin/gittool.php repos\n" .
32ae1ce4a6SAndreas Gohr            "$> ./bin/gittool.php origin -v"
339fb66494SAndreas Gohr        );
34c39ae2c9SAndreas Gohr
359fb66494SAndreas Gohr        $options->registerArgument(
369fb66494SAndreas Gohr            'command',
379fb66494SAndreas Gohr            'Command to execute. See below',
389fb66494SAndreas Gohr            true
399fb66494SAndreas Gohr        );
40c39ae2c9SAndreas Gohr
419fb66494SAndreas Gohr        $options->registerCommand(
429fb66494SAndreas Gohr            'clone',
439fb66494SAndreas Gohr            'Tries to install a known plugin or template (prefix with template:) via git. Uses the DokuWiki.org ' .
449fb66494SAndreas Gohr            'plugin repository to find the proper git repository. Multiple extensions can be given as parameters'
459fb66494SAndreas Gohr        );
469fb66494SAndreas Gohr        $options->registerArgument(
479fb66494SAndreas Gohr            'extension',
489fb66494SAndreas Gohr            'name of the extension to install, prefix with \'template:\' for templates',
499fb66494SAndreas Gohr            true,
509fb66494SAndreas Gohr            'clone'
519fb66494SAndreas Gohr        );
52c39ae2c9SAndreas Gohr
539fb66494SAndreas Gohr        $options->registerCommand(
549fb66494SAndreas Gohr            'install',
559fb66494SAndreas Gohr            'The same as clone, but when no git source repository can be found, the extension is installed via ' .
569fb66494SAndreas Gohr            'download'
579fb66494SAndreas Gohr        );
589fb66494SAndreas Gohr        $options->registerArgument(
599fb66494SAndreas Gohr            'extension',
609fb66494SAndreas Gohr            'name of the extension to install, prefix with \'template:\' for templates',
619fb66494SAndreas Gohr            true,
629fb66494SAndreas Gohr            'install'
639fb66494SAndreas Gohr        );
64c39ae2c9SAndreas Gohr
659fb66494SAndreas Gohr        $options->registerCommand(
669fb66494SAndreas Gohr            'repos',
679fb66494SAndreas Gohr            'Lists all git repositories found in this DokuWiki installation'
689fb66494SAndreas Gohr        );
69c39ae2c9SAndreas Gohr
709fb66494SAndreas Gohr        $options->registerCommand(
719fb66494SAndreas Gohr            '*',
729fb66494SAndreas Gohr            'Any unknown commands are assumed to be arguments to git and will be executed in all repositories ' .
739fb66494SAndreas Gohr            'found within this DokuWiki installation'
749fb66494SAndreas Gohr        );
75c39ae2c9SAndreas Gohr    }
76c39ae2c9SAndreas Gohr
77c39ae2c9SAndreas Gohr    /**
789fb66494SAndreas Gohr     * Your main program
799fb66494SAndreas Gohr     *
809fb66494SAndreas Gohr     * Arguments and options have been parsed when this is run
819fb66494SAndreas Gohr     *
82cbeaa4a0SAndreas Gohr     * @param Options $options
839fb66494SAndreas Gohr     * @return void
849fb66494SAndreas Gohr     */
85d868eb89SAndreas Gohr    protected function main(Options $options)
86d868eb89SAndreas Gohr    {
879fb66494SAndreas Gohr        $command = $options->getCmd();
88cbeaa4a0SAndreas Gohr        $args = $options->getArgs();
89cbeaa4a0SAndreas Gohr        if (!$command) $command = array_shift($args);
909fb66494SAndreas Gohr
919fb66494SAndreas Gohr        switch ($command) {
929fb66494SAndreas Gohr            case '':
931c36b3d8SAndreas Gohr                echo $options->help();
949fb66494SAndreas Gohr                break;
959fb66494SAndreas Gohr            case 'clone':
962b2d0ba9SAndreas Gohr                $this->cmdClone($args);
979fb66494SAndreas Gohr                break;
989fb66494SAndreas Gohr            case 'install':
992b2d0ba9SAndreas Gohr                $this->cmdInstall($args);
1009fb66494SAndreas Gohr                break;
1019fb66494SAndreas Gohr            case 'repo':
1029fb66494SAndreas Gohr            case 'repos':
1032b2d0ba9SAndreas Gohr                $this->cmdRepos();
1049fb66494SAndreas Gohr                break;
1059fb66494SAndreas Gohr            default:
1062b2d0ba9SAndreas Gohr                $this->cmdGit($command, $args);
1079fb66494SAndreas Gohr        }
1089fb66494SAndreas Gohr    }
1099fb66494SAndreas Gohr
1109fb66494SAndreas Gohr    /**
111c39ae2c9SAndreas Gohr     * Tries to install the given extensions using git clone
112c39ae2c9SAndreas Gohr     *
11342ea7f44SGerrit Uitslag     * @param array $extensions
114c39ae2c9SAndreas Gohr     */
115d868eb89SAndreas Gohr    public function cmdClone($extensions)
116d868eb89SAndreas Gohr    {
117b1f206e1SAndreas Gohr        $errors = [];
118b1f206e1SAndreas Gohr        $succeeded = [];
119c39ae2c9SAndreas Gohr
120c39ae2c9SAndreas Gohr        foreach ($extensions as $ext) {
121c39ae2c9SAndreas Gohr            $repo = $this->getSourceRepo($ext);
122c39ae2c9SAndreas Gohr
123c39ae2c9SAndreas Gohr            if (!$repo) {
1249fb66494SAndreas Gohr                $this->error("could not find a repository for $ext");
125c39ae2c9SAndreas Gohr                $errors[] = $ext;
126b1f206e1SAndreas Gohr            } elseif ($this->cloneExtension($ext, $repo)) {
127c39ae2c9SAndreas Gohr                $succeeded[] = $ext;
128c39ae2c9SAndreas Gohr            } else {
129c39ae2c9SAndreas Gohr                $errors[] = $ext;
130c39ae2c9SAndreas Gohr            }
131c39ae2c9SAndreas Gohr        }
132c39ae2c9SAndreas Gohr
133c39ae2c9SAndreas Gohr        echo "\n";
134b1f206e1SAndreas Gohr        if ($succeeded) $this->success('successfully cloned the following extensions: ' . implode(', ', $succeeded));
135b1f206e1SAndreas Gohr        if ($errors) $this->error('failed to clone the following extensions: ' . implode(', ', $errors));
136c39ae2c9SAndreas Gohr    }
137c39ae2c9SAndreas Gohr
138c39ae2c9SAndreas Gohr    /**
139c39ae2c9SAndreas Gohr     * Tries to install the given extensions using git clone with fallback to install
140c39ae2c9SAndreas Gohr     *
14142ea7f44SGerrit Uitslag     * @param array $extensions
142c39ae2c9SAndreas Gohr     */
143d868eb89SAndreas Gohr    public function cmdInstall($extensions)
144d868eb89SAndreas Gohr    {
145b1f206e1SAndreas Gohr        $errors = [];
146b1f206e1SAndreas Gohr        $succeeded = [];
147c39ae2c9SAndreas Gohr
148c39ae2c9SAndreas Gohr        foreach ($extensions as $ext) {
149c39ae2c9SAndreas Gohr            $repo = $this->getSourceRepo($ext);
150c39ae2c9SAndreas Gohr
151c39ae2c9SAndreas Gohr            if (!$repo) {
1529fb66494SAndreas Gohr                $this->info("could not find a repository for $ext");
153*1a7e82cdSAnna Dabrowska
154*1a7e82cdSAnna Dabrowska                try {
155*1a7e82cdSAnna Dabrowska                    $installer = new Installer();
156*1a7e82cdSAnna Dabrowska                    $this->info("installing $ext via download");
157*1a7e82cdSAnna Dabrowska                    $installer->installFromId($ext);
158*1a7e82cdSAnna Dabrowska                    $this->success("installed $ext via download");
159c39ae2c9SAndreas Gohr                    $succeeded[] = $ext;
160*1a7e82cdSAnna Dabrowska                } catch (\Exception $e) {
161*1a7e82cdSAnna Dabrowska                    $this->error("failed to install $ext via download");
162c39ae2c9SAndreas Gohr                    $errors[] = $ext;
163c39ae2c9SAndreas Gohr                }
164b1f206e1SAndreas Gohr            } elseif ($this->cloneExtension($ext, $repo)) {
165c39ae2c9SAndreas Gohr                $succeeded[] = $ext;
166c39ae2c9SAndreas Gohr            } else {
167c39ae2c9SAndreas Gohr                $errors[] = $ext;
168c39ae2c9SAndreas Gohr            }
169c39ae2c9SAndreas Gohr        }
170c39ae2c9SAndreas Gohr
171c39ae2c9SAndreas Gohr        echo "\n";
172b1f206e1SAndreas Gohr        if ($succeeded) $this->success('successfully installed the following extensions: ' . implode(', ', $succeeded));
173b1f206e1SAndreas Gohr        if ($errors) $this->error('failed to install the following extensions: ' . implode(', ', $errors));
174c39ae2c9SAndreas Gohr    }
175c39ae2c9SAndreas Gohr
176c39ae2c9SAndreas Gohr    /**
177c39ae2c9SAndreas Gohr     * Executes the given git command in every repository
178c39ae2c9SAndreas Gohr     *
179c39ae2c9SAndreas Gohr     * @param $cmd
180c39ae2c9SAndreas Gohr     * @param $arg
181c39ae2c9SAndreas Gohr     */
182d868eb89SAndreas Gohr    public function cmdGit($cmd, $arg)
183d868eb89SAndreas Gohr    {
184c39ae2c9SAndreas Gohr        $repos = $this->findRepos();
185c39ae2c9SAndreas Gohr
186b1f206e1SAndreas Gohr        $shell = array_merge(['git', $cmd], $arg);
187c39ae2c9SAndreas Gohr        $shell = array_map('escapeshellarg', $shell);
188b1f206e1SAndreas Gohr        $shell = implode(' ', $shell);
189c39ae2c9SAndreas Gohr
190c39ae2c9SAndreas Gohr        foreach ($repos as $repo) {
191c39ae2c9SAndreas Gohr            if (!@chdir($repo)) {
1929fb66494SAndreas Gohr                $this->error("Could not change into $repo");
193c39ae2c9SAndreas Gohr                continue;
194c39ae2c9SAndreas Gohr            }
195c39ae2c9SAndreas Gohr
1969fb66494SAndreas Gohr            $this->info("executing $shell in $repo");
197c39ae2c9SAndreas Gohr            $ret = 0;
198c39ae2c9SAndreas Gohr            system($shell, $ret);
199c39ae2c9SAndreas Gohr
200c39ae2c9SAndreas Gohr            if ($ret == 0) {
2019fb66494SAndreas Gohr                $this->success("git succeeded in $repo");
202c39ae2c9SAndreas Gohr            } else {
2039fb66494SAndreas Gohr                $this->error("git failed in $repo");
204c39ae2c9SAndreas Gohr            }
205c39ae2c9SAndreas Gohr        }
206c39ae2c9SAndreas Gohr    }
207c39ae2c9SAndreas Gohr
208c39ae2c9SAndreas Gohr    /**
209c39ae2c9SAndreas Gohr     * Simply lists the repositories
210c39ae2c9SAndreas Gohr     */
211d868eb89SAndreas Gohr    public function cmdRepos()
212d868eb89SAndreas Gohr    {
213c39ae2c9SAndreas Gohr        $repos = $this->findRepos();
214c39ae2c9SAndreas Gohr        foreach ($repos as $repo) {
215c39ae2c9SAndreas Gohr            echo "$repo\n";
216c39ae2c9SAndreas Gohr        }
217c39ae2c9SAndreas Gohr    }
218c39ae2c9SAndreas Gohr
219c39ae2c9SAndreas Gohr    /**
220c39ae2c9SAndreas Gohr     * Clones the extension from the given repository
221c39ae2c9SAndreas Gohr     *
222c39ae2c9SAndreas Gohr     * @param string $ext
223c39ae2c9SAndreas Gohr     * @param string $repo
224c39ae2c9SAndreas Gohr     * @return bool
225c39ae2c9SAndreas Gohr     */
226d868eb89SAndreas Gohr    private function cloneExtension($ext, $repo)
227d868eb89SAndreas Gohr    {
2281b2deed9Sfiwswe        if (str_starts_with($ext, 'template:')) {
229c39ae2c9SAndreas Gohr            $target = fullpath(tpl_incdir() . '../' . substr($ext, 9));
230c39ae2c9SAndreas Gohr        } else {
231c39ae2c9SAndreas Gohr            $target = DOKU_PLUGIN . $ext;
232c39ae2c9SAndreas Gohr        }
233c39ae2c9SAndreas Gohr
2349fb66494SAndreas Gohr        $this->info("cloning $ext from $repo to $target");
235c39ae2c9SAndreas Gohr        $ret = 0;
236c39ae2c9SAndreas Gohr        system("git clone $repo $target", $ret);
237c39ae2c9SAndreas Gohr        if ($ret === 0) {
2389fb66494SAndreas Gohr            $this->success("cloning of $ext succeeded");
239c39ae2c9SAndreas Gohr            return true;
240c39ae2c9SAndreas Gohr        } else {
2419fb66494SAndreas Gohr            $this->error("cloning of $ext failed");
242c39ae2c9SAndreas Gohr            return false;
243c39ae2c9SAndreas Gohr        }
244c39ae2c9SAndreas Gohr    }
245c39ae2c9SAndreas Gohr
246c39ae2c9SAndreas Gohr    /**
247c39ae2c9SAndreas Gohr     * Returns all git repositories in this DokuWiki install
248c39ae2c9SAndreas Gohr     *
249c39ae2c9SAndreas Gohr     * Looks in root, template and plugin directories only.
250c39ae2c9SAndreas Gohr     *
251c39ae2c9SAndreas Gohr     * @return array
252c39ae2c9SAndreas Gohr     */
253d868eb89SAndreas Gohr    private function findRepos()
254d868eb89SAndreas Gohr    {
2559fb66494SAndreas Gohr        $this->info('Looking for .git directories');
256c39ae2c9SAndreas Gohr        $data = array_merge(
257c39ae2c9SAndreas Gohr            glob(DOKU_INC . '.git', GLOB_ONLYDIR),
258c39ae2c9SAndreas Gohr            glob(DOKU_PLUGIN . '*/.git', GLOB_ONLYDIR),
259c39ae2c9SAndreas Gohr            glob(fullpath(tpl_incdir() . '../') . '/*/.git', GLOB_ONLYDIR)
260c39ae2c9SAndreas Gohr        );
261c39ae2c9SAndreas Gohr
262c39ae2c9SAndreas Gohr        if (!$data) {
2639fb66494SAndreas Gohr            $this->error('Found no .git directories');
264c39ae2c9SAndreas Gohr        } else {
2659fb66494SAndreas Gohr            $this->success('Found ' . count($data) . ' .git directories');
266c39ae2c9SAndreas Gohr        }
267b0c7084fSAndreas Gohr        $data = array_map('fullpath', array_map('dirname', $data));
268c39ae2c9SAndreas Gohr        return $data;
269c39ae2c9SAndreas Gohr    }
270c39ae2c9SAndreas Gohr
271c39ae2c9SAndreas Gohr    /**
272c39ae2c9SAndreas Gohr     * Returns the repository for the given extension
273c39ae2c9SAndreas Gohr     *
274*1a7e82cdSAnna Dabrowska     * @param string $extensionId
27542ea7f44SGerrit Uitslag     * @return false|string
276c39ae2c9SAndreas Gohr     */
277*1a7e82cdSAnna Dabrowska    private function getSourceRepo($extensionId)
278d868eb89SAndreas Gohr    {
279*1a7e82cdSAnna Dabrowska        $extension = Extension::createFromId($extensionId);
2807e8500eeSGerrit Uitslag
281*1a7e82cdSAnna Dabrowska        $repourl = $extension->getSourcerepoURL();
28268cf024bSAndreas Gohr        if (!$repourl) return false;
28368cf024bSAndreas Gohr
284c39ae2c9SAndreas Gohr        // match github repos
28568cf024bSAndreas Gohr        if (preg_match('/github\.com\/([^\/]+)\/([^\/]+)/i', $repourl, $m)) {
286c39ae2c9SAndreas Gohr            $user = $m[1];
287c39ae2c9SAndreas Gohr            $repo = $m[2];
288c39ae2c9SAndreas Gohr            return 'https://github.com/' . $user . '/' . $repo . '.git';
289c39ae2c9SAndreas Gohr        }
290c39ae2c9SAndreas Gohr
291c39ae2c9SAndreas Gohr        // match gitorious repos
29268cf024bSAndreas Gohr        if (preg_match('/gitorious.org\/([^\/]+)\/([^\/]+)?/i', $repourl, $m)) {
293c39ae2c9SAndreas Gohr            $user = $m[1];
294c39ae2c9SAndreas Gohr            $repo = $m[2];
295c39ae2c9SAndreas Gohr            if (!$repo) $repo = $user;
296c39ae2c9SAndreas Gohr
297c39ae2c9SAndreas Gohr            return 'https://git.gitorious.org/' . $user . '/' . $repo . '.git';
298c39ae2c9SAndreas Gohr        }
299c39ae2c9SAndreas Gohr
300c39ae2c9SAndreas Gohr        // match bitbucket repos - most people seem to use mercurial there though
30168cf024bSAndreas Gohr        if (preg_match('/bitbucket\.org\/([^\/]+)\/([^\/]+)/i', $repourl, $m)) {
302c39ae2c9SAndreas Gohr            $user = $m[1];
303c39ae2c9SAndreas Gohr            $repo = $m[2];
304c39ae2c9SAndreas Gohr            return 'https://bitbucket.org/' . $user . '/' . $repo . '.git';
305c39ae2c9SAndreas Gohr        }
306c39ae2c9SAndreas Gohr
307c39ae2c9SAndreas Gohr        return false;
308c39ae2c9SAndreas Gohr    }
309c39ae2c9SAndreas Gohr}
310c39ae2c9SAndreas Gohr
311b0b7909bSAndreas Gohr// Main
312b0b7909bSAndreas Gohr$cli = new GitToolCLI();
313b0b7909bSAndreas Gohr$cli->run();
314