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