xref: /dokuwiki/bin/gittool.php (revision d0c0a5c427963f7daba8ff28ee1abab3ef8156ba)
1#!/usr/bin/php
2<?php
3if(!defined('DOKU_INC')) define('DOKU_INC', realpath(dirname(__FILE__).'/../').'/');
4define('NOSESSION', 1);
5require_once(DOKU_INC.'inc/init.php');
6
7/**
8 * Easily manage DokuWiki git repositories
9 *
10 * @author Andreas Gohr <andi@splitbrain.org>
11 */
12class GitToolCLI extends DokuCLI {
13
14    /**
15     * Register options and arguments on the given $options object
16     *
17     * @param DokuCLI_Options $options
18     * @return void
19     */
20    protected function setup(DokuCLI_Options $options) {
21        $options->setHelp(
22            "Manage git repositories for DokuWiki and its plugins and templates.\n\n".
23            "$> ./bin/gittool.php clone gallery template:ach\n".
24            "$> ./bin/gittool.php repos\n".
25            "$> ./bin/gittool.php origin -v"
26        );
27
28        $options->registerArgument(
29            'command',
30            'Command to execute. See below',
31            true
32        );
33
34        $options->registerCommand(
35            'clone',
36            'Tries to install a known plugin or template (prefix with template:) via git. Uses the DokuWiki.org '.
37            'plugin repository to find the proper git repository. Multiple extensions can be given as parameters'
38        );
39        $options->registerArgument(
40            'extension',
41            'name of the extension to install, prefix with \'template:\' for templates',
42            true,
43            'clone'
44        );
45
46        $options->registerCommand(
47            'install',
48            'The same as clone, but when no git source repository can be found, the extension is installed via '.
49            'download'
50        );
51        $options->registerArgument(
52            'extension',
53            'name of the extension to install, prefix with \'template:\' for templates',
54            true,
55            'install'
56        );
57
58        $options->registerCommand(
59            'repos',
60            'Lists all git repositories found in this DokuWiki installation'
61        );
62
63        $options->registerCommand(
64            '*',
65            'Any unknown commands are assumed to be arguments to git and will be executed in all repositories '.
66            'found within this DokuWiki installation'
67        );
68    }
69
70    /**
71     * Your main program
72     *
73     * Arguments and options have been parsed when this is run
74     *
75     * @param DokuCLI_Options $options
76     * @return void
77     */
78    protected function main(DokuCLI_Options $options) {
79        $command = $options->getCmd();
80        if(!$command) $command = array_shift($options->args);
81
82        switch($command) {
83            case '':
84                echo $options->help();
85                break;
86            case 'clone':
87                $this->cmd_clone($options->args);
88                break;
89            case 'install':
90                $this->cmd_install($options->args);
91                break;
92            case 'repo':
93            case 'repos':
94                $this->cmd_repos();
95                break;
96            default:
97                $this->cmd_git($command, $options->args);
98        }
99    }
100
101    /**
102     * Tries to install the given extensions using git clone
103     *
104     * @param array      $extensions
105     */
106    public function cmd_clone($extensions) {
107        $errors    = array();
108        $succeeded = array();
109
110        foreach($extensions as $ext) {
111            $repo = $this->getSourceRepo($ext);
112
113            if(!$repo) {
114                $this->error("could not find a repository for $ext");
115                $errors[] = $ext;
116            } else {
117                if($this->cloneExtension($ext, $repo)) {
118                    $succeeded[] = $ext;
119                } else {
120                    $errors[] = $ext;
121                }
122            }
123        }
124
125        echo "\n";
126        if($succeeded) $this->success('successfully cloned the following extensions: '.join(', ', $succeeded));
127        if($errors) $this->error('failed to clone the following extensions: '.join(', ', $errors));
128    }
129
130    /**
131     * Tries to install the given extensions using git clone with fallback to install
132     *
133     * @param array      $extensions
134     */
135    public function cmd_install($extensions) {
136        $errors    = array();
137        $succeeded = array();
138
139        foreach($extensions as $ext) {
140            $repo = $this->getSourceRepo($ext);
141
142            if(!$repo) {
143                $this->info("could not find a repository for $ext");
144                if($this->downloadExtension($ext)) {
145                    $succeeded[] = $ext;
146                } else {
147                    $errors[] = $ext;
148                }
149            } else {
150                if($this->cloneExtension($ext, $repo)) {
151                    $succeeded[] = $ext;
152                } else {
153                    $errors[] = $ext;
154                }
155            }
156        }
157
158        echo "\n";
159        if($succeeded) $this->success('successfully installed the following extensions: '.join(', ', $succeeded));
160        if($errors) $this->error('failed to install the following extensions: '.join(', ', $errors));
161    }
162
163    /**
164     * Executes the given git command in every repository
165     *
166     * @param $cmd
167     * @param $arg
168     */
169    public function cmd_git($cmd, $arg) {
170        $repos = $this->findRepos();
171
172        $shell = array_merge(array('git', $cmd), $arg);
173        $shell = array_map('escapeshellarg', $shell);
174        $shell = join(' ', $shell);
175
176        foreach($repos as $repo) {
177            if(!@chdir($repo)) {
178                $this->error("Could not change into $repo");
179                continue;
180            }
181
182            echo "\n";
183            $this->info("executing $shell in $repo");
184            $ret = 0;
185            system($shell, $ret);
186
187            if($ret == 0) {
188                $this->success("git succeeded in $repo");
189            } else {
190                $this->error("git failed in $repo");
191            }
192        }
193    }
194
195    /**
196     * Simply lists the repositories
197     */
198    public function cmd_repos() {
199        $repos = $this->findRepos();
200        foreach($repos as $repo) {
201            echo "$repo\n";
202        }
203    }
204
205    /**
206     * Install extension from the given download URL
207     *
208     * @param string $ext
209     * @return bool|null
210     */
211    private function downloadExtension($ext) {
212        /** @var helper_plugin_extension_extension $plugin */
213        $plugin = plugin_load('helper', 'extension_extension');
214        if(!$ext) die("extension plugin not available, can't continue");
215
216        $plugin->setExtension($ext);
217
218        $url = $plugin->getDownloadURL();
219        if(!$url) {
220            $this->error("no download URL for $ext");
221            return false;
222        }
223
224        $ok = false;
225        try {
226            $this->info("installing $ext via download from $url");
227            $ok = $plugin->installFromURL($url);
228        } catch(Exception $e) {
229            $this->error($e->getMessage());
230        }
231
232        if($ok) {
233            $this->success("installed $ext via download");
234            return true;
235        } else {
236            $this->success("failed to install $ext via download");
237            return false;
238        }
239    }
240
241    /**
242     * Clones the extension from the given repository
243     *
244     * @param string $ext
245     * @param string $repo
246     * @return bool
247     */
248    private function cloneExtension($ext, $repo) {
249        if(substr($ext, 0, 9) == 'template:') {
250            $target = fullpath(tpl_incdir().'../'.substr($ext, 9));
251        } else {
252            $target = DOKU_PLUGIN.$ext;
253        }
254
255        $this->info("cloning $ext from $repo to $target");
256        $ret = 0;
257        system("git clone $repo $target", $ret);
258        if($ret === 0) {
259            $this->success("cloning of $ext succeeded");
260            return true;
261        } else {
262            $this->error("cloning of $ext failed");
263            return false;
264        }
265    }
266
267    /**
268     * Returns all git repositories in this DokuWiki install
269     *
270     * Looks in root, template and plugin directories only.
271     *
272     * @return array
273     */
274    private function findRepos() {
275        $this->info('Looking for .git directories');
276        $data = array_merge(
277            glob(DOKU_INC.'.git', GLOB_ONLYDIR),
278            glob(DOKU_PLUGIN.'*/.git', GLOB_ONLYDIR),
279            glob(fullpath(tpl_incdir().'../').'/*/.git', GLOB_ONLYDIR)
280        );
281
282        if(!$data) {
283            $this->error('Found no .git directories');
284        } else {
285            $this->success('Found '.count($data).' .git directories');
286        }
287        $data = array_map('fullpath', array_map('dirname', $data));
288        return $data;
289    }
290
291    /**
292     * Returns the repository for the given extension
293     *
294     * @param $extension
295     * @return false|string
296     */
297    private function getSourceRepo($extension) {
298        /** @var helper_plugin_extension_extension $ext */
299        $ext = plugin_load('helper', 'extension_extension');
300        if(!$ext) die("extension plugin not available, can't continue");
301
302        $ext->setExtension($extension);
303
304        $repourl = $ext->getSourcerepoURL();
305        if(!$repourl) return false;
306
307        // match github repos
308        if(preg_match('/github\.com\/([^\/]+)\/([^\/]+)/i', $repourl, $m)) {
309            $user = $m[1];
310            $repo = $m[2];
311            return 'https://github.com/'.$user.'/'.$repo.'.git';
312        }
313
314        // match gitorious repos
315        if(preg_match('/gitorious.org\/([^\/]+)\/([^\/]+)?/i', $repourl, $m)) {
316            $user = $m[1];
317            $repo = $m[2];
318            if(!$repo) $repo = $user;
319
320            return 'https://git.gitorious.org/'.$user.'/'.$repo.'.git';
321        }
322
323        // match bitbucket repos - most people seem to use mercurial there though
324        if(preg_match('/bitbucket\.org\/([^\/]+)\/([^\/]+)/i', $repourl, $m)) {
325            $user = $m[1];
326            $repo = $m[2];
327            return 'https://bitbucket.org/'.$user.'/'.$repo.'.git';
328        }
329
330        return false;
331    }
332}
333
334// Main
335$cli = new GitToolCLI();
336$cli->run();