Completed
Push — master ( 786b76...4e834f )
by Basil
02:43
created

RepoController::rebaseRepo()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 13
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 13
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 8
nc 1
nop 2
1
<?php
2
3
namespace luya\dev;
4
5
use Curl\Curl;
6
use GitWrapper\GitWrapper;
7
use yii\console\widgets\Table;
8
use yii\console\Markdown;
9
use yii\helpers\Console;
10
use luya\helpers\FileHelper;
11
12
/**
13
 * Dev Env cloning and updating.
14
 *
15
 * Provdes functions to clone and update the repos.
16
 *
17
 * Usage
18
 *
19
 * ```sh
20
 * ./vendor/bin/luyadev repo/init
21
 * ./vendor/bin/luyadev repo/update
22
 * ```
23
 *
24
 * Or clone a custom repo into the repos folder:
25
 *
26
 * ```sh
27
 * ./venodr/bin/luyadev repo/clone luya-module-news luyadev
28
 * ```
29
 *
30
 * In order to remove an existing repo from update list
31
 * 
32
 * ```sh
33
 * ./vendor/bin/luyadev repo/remove luya-module-news
34
 * ```
35
 *
36
 * @author Basil Suter <[email protected]>
37
 * @since 1.0.1
38
 */
39
class RepoController extends BaseDevCommand
40
{
41
    const CONFIG_VAR_USERNAME = 'username';
42
    
43
    const CONFIG_VAR_CLONETYPE = 'cloneType';
44
    
45
    const CONFIG_VAR_CUSTOMCLONES = 'customClones';
46
    
47
    /**
48
     * @var string Default action is actionInit();
49
     */
50
    public $defaultAction = 'init';
51
    
52
    /**
53
     * @var array The default repos from luyadev
54
     */
55
    public $repos = [
56
        'luya',
57
        'luya-module-admin',
58
        'luya-module-cms',
59
    ];
60
    
61
    public $text = <<<EOT
62
**CLONE REPOS**
63
64
We've detected that you don't have all module repos forked to your account. You can only push changes to the forked repos, all others are **READ ONLY**.
65
66
If you want to work on a specific repo, make sure that repo is forked to your Github account.
67
68
You can also skip this command, fork the repos and rerun this command again.
69
70
**FORK ME**
71
EOT;
72
    
73
    /**
74
     * Initilize the main repos.
75
     *
76
     * @return number
77
     */
78
    public function actionInit()
79
    {
80
        // username
81
        $username = $this->getConfig(self::CONFIG_VAR_USERNAME);
82
        if (!$username) {
83
            $username = $this->prompt('Whats your Github username?');
84
            $this->saveConfig(self::CONFIG_VAR_USERNAME, $username);
85
        }
86
        
87
        // clonetype
88
        $cloneType = $this->getConfig(self::CONFIG_VAR_CLONETYPE);
89
        if (!$cloneType) {
90
            $cloneType = $this->select('Are you connected via ssh or https?', ['ssh' => 'ssh', 'http' => 'http']);
91
            $this->saveConfig(self::CONFIG_VAR_CLONETYPE, $cloneType);
92
        }
93
        
94
        $summary = [];
95
        $itemWithoutFork = false;
96
        
97
        // generate summary overview
98
        foreach ($this->repos as $repo) {
99
            $newRepoHome = $this->getFilesystemRepoPath($repo);
100
            if (file_exists($newRepoHome . DIRECTORY_SEPARATOR . '.git')) {
101
                $summary[] = $this->summaryItem($repo, false, true);
0 ignored issues
show
Documentation introduced by
false is of type boolean, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
Documentation introduced by
true is of type boolean, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
102
            } elseif ($this->forkExists($username, $repo)) {
0 ignored issues
show
Bug introduced by
It seems like $username defined by $this->getConfig(self::CONFIG_VAR_USERNAME) on line 81 can also be of type boolean; however, luya\dev\RepoController::forkExists() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
103
                $summary[] = $this->summaryItem($repo, true, false);
0 ignored issues
show
Documentation introduced by
true is of type boolean, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
Documentation introduced by
false is of type boolean, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
104
            } else {
105
                $itemWithoutFork = true;
106
                $summary[] = $this->summaryItem($repo, false, false);
0 ignored issues
show
Documentation introduced by
false is of type boolean, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
107
            }
108
        }
109
        
110
        if ($itemWithoutFork) {
111
            Console::clearScreen();
112
            $this->outputInfo($this->markdown($this->text));
113
            foreach ($summary as $sum) {
114
                if (!$sum[2] && !$sum[1]) {
115
                    $this->outputInfo($this->markdown("**{$sum[0]}**: https://github.com/luyadev/{$sum[0]}/fork", true));
116
                }
117
            }
118
            echo (new Table())->setHeaders(['Repo', 'Already initialized', 'Fork exists'])->setRows($summary)->run();
119
            $this->outputError("Repos without fork detected. Those repos will be initialized as READ ONLY. It means you can not push any changes to them.");
120
            
121
            if (!$this->confirm("Continue?")) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->confirm('Continue?') of type boolean|null is loosely compared to false; this is ambiguous if the boolean can be false. You might want to explicitly use !== null instead.

If an expression can have both false, and null as possible values. It is generally a good practice to always use strict comparison to clearly distinguish between those two values.

$a = canBeFalseAndNull();

// Instead of
if ( ! $a) { }

// Better use one of the explicit versions:
if ($a !== null) { }
if ($a !== false) { }
if ($a !== null && $a !== false) { }
Loading history...
122
                return $this->outputError('Abort by User.');
123
            }
124
        }
125
        
126
        // foreach summary and clone
0 ignored issues
show
Unused Code Comprehensibility introduced by
38% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
127
        foreach ($summary as $sum) {
128
            $repo = $sum[0];
129
            $hasFork = $sum[2];
130
            $exists = $sum[1];
131
            
132
            // continue already initialized repos.
133
            if ($exists) {
134
                continue;
135
            }
136
            
137
            $newRepoHome = $this->getFilesystemRepoPath($repo);
138
            
139
            if ($hasFork) {
140
                $cloneUrl = ($cloneType == 'ssh') ? "[email protected]:{$username}/{$repo}.git" : "https://github.com/{$username}/{$repo}.git";
141
            } else {
142
                $cloneUrl = ($cloneType == 'ssh') ? "[email protected]:luyadev/{$repo}.git" : "https://github.com/{$username}/{$repo}.git";
143
            }
144
            
145
            $this->cloneRepo($repo, $cloneUrl, $newRepoHome, 'luyadev');
146
        }
147
        
148
        return $this->outputSuccess("init complete.");
149
    }
150
    
151
    /**
152
     * Update all repos to master branch from upstream.
153
     */
154
    public function actionUpdate()
155
    {
156
        foreach ($this->repos as $repo) {
157
        	$this->rebaseRepo($repo, $this->getFilesystemRepoPath($repo));
158
        }
159
        
160
        foreach ($this->getConfig(self::CONFIG_VAR_CUSTOMCLONES, []) as $repo => $path) {
0 ignored issues
show
Bug introduced by
The expression $this->getConfig(self::C..._CUSTOMCLONES, array()) of type boolean is not traversable.
Loading history...
161
        	$this->rebaseRepo($repo, $path);
162
        }
163
    }
164
    
165
    /**
166
     * Clone a repo into the repos folder.
167
     * 
168
     * @param string $repo
169
     * @param string $vendor
170
     */
171
    public function actionClone($vendor = null, $repo = null)
172
    {
173
    	// if `vendor/repo` notation is provided
174
    	if ($vendor !== null && strpos($vendor, '/')) {
175
    		list($vendor, $repo) = explode("/", $vendor);	
176
    	}
177
    	
178
        if (empty($vendor)) {
179
            $vendor = $this->prompt("Enter the username/vendor for this repo (e.g. luyadev)");
180
        }
181
        
182
        if (empty($repo)) {
183
        	$repo = $this->prompt("Enter the name of the repo you like to clone (e.g. luya-module-news)");
184
        }
185
        
186
        $clones = $this->getConfig(self::CONFIG_VAR_CUSTOMCLONES, []);
187
        
188
        $repoFileSystemPath = $this->getFilesystemRepoPath($repo);
189
        
190
        $clones[$repo] = $repoFileSystemPath;
191
        
192
        $this->cloneRepo($repo, $this->getCloneUrlBasedOnType($repo, $vendor), $repoFileSystemPath, $vendor);
193
        
194
        $this->saveConfig(self::CONFIG_VAR_CUSTOMCLONES, $clones);
195
    }
196
    
197
    /**
198
     * Remove a given repo from filesystem.
199
     * 
200
     * @param string $repo The repo name like `luya-module-cms` without vendor.
201
     */
202
    public function actionRemove($repo)
203
    {
204
    	FileHelper::removeDirectory($this->getFilesystemRepoPath($repo));
205
    	$clones = $this->getConfig(self::CONFIG_VAR_CUSTOMCLONES, []);
206
    	if (isset($clones[$repo])) {
207
	    	unset($clones[$repo]);
208
	    	$this->saveConfig(self::CONFIG_VAR_CUSTOMCLONES, $clones);
209
    	}
210
    	
211
    	return $this->outputSuccess("Removed repo {$repo}.");
212
    }
213
    
214
    private $_gitWrapper;
215
    
216
    /**
217
     * @return \GitWrapper\GitWrapper
218
     */
219
    protected function getGitWrapper()
220
    {
221
        if ($this->_gitWrapper === null) {
222
            $this->_gitWrapper = new GitWrapper();
223
            $this->_gitWrapper->setTimeout(300);
224
        }
225
    
226
        return $this->_gitWrapper;
227
    }
228
    
229
    /**
230
     * 
231
     * @param string $repo
232
     * @param string $isFork
233
     * @param string $exists
234
     * @return array
235
     */
236
    private function summaryItem($repo, $isFork, $exists)
237
    {
238
        return [$repo, $exists, $isFork];
239
    }
240
    
241
    /**
242
     * 
243
     * @param string $repo
244
     * @return string
245
     */
246
    private function getFilesystemRepoPath($repo)
247
    {
248
        return 'repos' . DIRECTORY_SEPARATOR . $repo;
249
    }
250
    
251
    /**
252
     * 
253
     * @param string $username
254
     * @param string $repo
255
     * @return boolean
256
     */
257
    private function forkExists($username, $repo)
258
    {
259
        return (new Curl())->get('https://api.github.com/repos/'.$username.'/'.$repo)->isSuccess();
260
    }
261
    
262
    /**
263
     * 
264
     * @param string $text
265
     * @param boolean $paragraph
266
     * @return string
267
     */
268
    private function markdown($text, $paragraph = false)
269
    {
270
        $parser = new Markdown();
271
    
272
        if ($paragraph) {
273
            return $parser->parseParagraph($text);
274
        }
275
    
276
        return $parser->parse($text);
277
    }
278
    
279
    /**
280
     * Return the url to clone based on config clone type (ssh/https).
281
     *
282
     * @param string $repo
283
     * @param string $username
284
     * @return string
285
     */
286
    private function getCloneUrlBasedOnType($repo, $username)
287
    {
288
        return ($this->getConfig(self::CONFIG_VAR_CLONETYPE) == 'ssh') ? "[email protected]:{$username}/{$repo}.git" : "https://github.com/{$username}/{$repo}.git";
289
    }
290
    
291
    /**
292
     * Rebase existing repo.
293
     * 
294
     * @param string $repo
295
     * @param string $repoFileSystemPath
296
     */
297
    private function rebaseRepo($repo, $repoFileSystemPath)
298
    {
299
    	$wrapper = new GitWrapper();
300
    	 
301
    	$wrapper->git('checkout master', $repoFileSystemPath);
302
    	$this->outputInfo("{$repo}: checkout master ✔");
303
    	 
304
    	$wrapper->git('fetch upstream', $repoFileSystemPath);
305
    	$this->outputInfo("{$repo}: fetch upstream ✔");
306
    	 
307
    	$wrapper->git('rebase upstream/master master', $repoFileSystemPath);
308
    	$this->outputInfo("{$repo}: rebase master ✔");
309
    }
310
    
311
    /**
312
     * Clone a repo into the repos folder.
313
     *
314
     * @param string $repo
315
     * @param string $cloneUrl
316
     * @param string $newRepoHome
317
     * @param string $upstreamUsername The upstream vendor name of the repo if available.
318
     */
319
    private function cloneRepo($repo, $cloneUrl, $newRepoHome, $upstreamUsername)
320
    {
321
        $this->outputSuccess("{$repo}: cloning {$cloneUrl} ...");
322
        $this->getGitWrapper()->cloneRepository($cloneUrl, $newRepoHome);
323
        
324
        if (!empty($upstreamUsername)) {
325
            $this->getGitWrapper()->git('remote add upstream https://github.com/'.$upstreamUsername.'/'.$repo.'.git', $newRepoHome);
326
            $this->outputInfo("Configure upstream https://github.com/{$upstreamUsername}/{$repo}.git");
327
        }
328
        
329
        $this->outputSuccess("{$repo}: ✔ complete");
330
    }
331
}
332