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