Completed
Push — master ( da729e...fa941f )
by Basil
04:01
created

RepoController::actionClone()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 12
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 12
rs 9.4285
c 0
b 0
f 0
cc 3
eloc 6
nc 4
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
11
/**
12
 * Dev Env cloning and updating.
13
 * 
14
 * Provdes functions to clone and update the repos.
15
 * 
16
 * Usage
17
 * 
18
 * ```sh
19
 * ./vendor/bin/luyadev repo/init
20
 * ./vendor/bin/luyadev repo/update
21
 * ```
22
 * 
23
 * Or clone a custom repo into the repos folder:
24
 * 
25
 * ```sh
26
 * ./venodr/bin/luyadev repo/clone luya-module-news luyadev
27
 * ```
28
 * 
29
 * @author Basil Suter <[email protected]>
30
 * @since 1.0.1
31
 */
32
class RepoController extends BaseDevCommand
33
{
34
	const CONFIG_VAR_USERNAME = 'username';
35
	
36
	const CONFIG_VAR_CLONETYPE = 'cloneType';
37
	
38
	/**
39
	 * @var string Default action is actionInit();
40
	 */
41
	public $defaultAction = 'init';
42
	
43
	/**
44
	 * @var array The default repos from luyadev
45
	 */
46
    public $repos = [
47
        'luya',
48
        'luya-module-admin',
49
        'luya-module-cms',
50
    ];
51
    
52
    public $text = <<<EOT
53
**CLONE REPOS**
54
55
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**.
56
57
If you want to work on a specific repo, make sure that repo is forked to your Github account.
58
59
You can also skip this command, fork the repos and rerun this command again.
60
61
**FORK ME**
62
EOT;
63
    
64
    /**
65
     * Initilize the main repos.
66
     * 
67
     * @return number
68
     */
69
    public function actionInit()
70
    {
71
        // username
72
        $username = $this->getConfig(self::CONFIG_VAR_USERNAME);
73
        if (!$username) {
74
            $username = $this->prompt('Whats your Github username?');
75
            $this->saveConfig(self::CONFIG_VAR_USERNAME, $username);
76
        }
77
        
78
        // clonetype
79
        $cloneType = $this->getConfig(self::CONFIG_VAR_CLONETYPE);
80
        if (!$cloneType) {
81
            $cloneType = $this->select('Are you connected via ssh or https?', ['ssh' => 'ssh', 'http' => 'http']);
82
            $this->saveConfig(self::CONFIG_VAR_CLONETYPE, $cloneType);
83
        }
84
        
85
        $summary = [];
86
        $itemWithoutFork = false;
87
        
88
        // generate summary overview
89
        foreach ($this->repos as $repo) {
90
            $newRepoHome = $this->getFilesystemRepoPath($repo);
91
            if (file_exists($newRepoHome . DIRECTORY_SEPARATOR . '.git')) {
92
                $summary[] = $this->summaryItem($repo, false, true);
93
            } elseif ($this->forkExists($username, $repo)) {
94
                $summary[] = $this->summaryItem($repo, true, false);
95
            } else {
96
                $itemWithoutFork = true;
97
                $summary[] = $this->summaryItem($repo, false, false);
98
            }
99
        }
100
        
101
        if ($itemWithoutFork) {
102
            Console::clearScreen();
103
            $this->outputInfo($this->markdown($this->text));
104
            foreach ($summary as $sum) {
105
                if (!$sum[2] && !$sum[1]) {
106
                    $this->outputInfo($this->markdown("**{$sum[0]}**: https://github.com/luyadev/{$sum[0]}/fork", true));
107
                }
108
            }
109
            echo (new Table())->setHeaders(['Repo', 'Already initialized', 'Fork exists'])->setRows($summary)->run();
110
            $this->outputError("Repos without fork detected. Those repos will be initialized as READ ONLY. It means you can not push any changes to them.");
111
            
112
            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...
113
                return $this->outputError('Abort by User.');
114
            }
115
        }
116
        
117
        // 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...
118
        foreach ($summary as $sum) {
119
            $repo = $sum[0];
120
            $hasFork = $sum[2];
121
            $exists = $sum[1];
122
            
123
            // continue already initialized repos.
124
            if ($exists) {
125
                continue;
126
            }
127
            
128
            $newRepoHome = $this->getFilesystemRepoPath($repo);
129
            
130
            if ($hasFork) {
131
                $cloneUrl = ($cloneType == 'ssh') ? "[email protected]:{$username}/{$repo}.git" : "https://github.com/{$username}/{$repo}.git";
132
            } else {
133
                $cloneUrl = ($cloneType == 'ssh') ? "[email protected]:luyadev/{$repo}.git" : "https://github.com/{$username}/{$repo}.git";
134
            }
135
            
136
            $this->cloneRepo($repo, $cloneUrl, $newRepoHome, 'luyadev');
137
        }
138
        
139
        return $this->outputSuccess("init complete.");
140
    }
141
    
142
    /**
143
     * Update all repos to master branch from upstream.
144
     */
145
    public function actionUpdate()
146
    {
147
        $wrapper = new GitWrapper();
148
        
149
        foreach ($this->repos as $repo) {
150
            $wrapper->git('checkout master',  'repos' . DIRECTORY_SEPARATOR . $repo);
151
            $this->outputInfo("{$repo}: checkout master ✔");
152
            
153
            $wrapper->git('fetch upstream',  'repos' . DIRECTORY_SEPARATOR . $repo);
154
            $this->outputInfo("{$repo}: fetch upstream ✔");
155
            
156
            $wrapper->git('rebase upstream/master master',  'repos' . DIRECTORY_SEPARATOR . $repo);
157
            $this->outputInfo("{$repo}: rebase master ✔");
158
        }
159
    }
160
    
161
    /**
162
     * 
163
     * @param unknown $repo
164
     * @param unknown $vendor
165
     * @return unknown
166
     */
167
    public function actionClone($repo = null, $vendor = null)
168
    {
169
    	if (empty($repo)) {
170
    		$repo = $this->prompt("Enter the name of the repo you like to clone (e.g. luya-module-news)");
171
    	}
172
    	
173
    	if (empty($vendor)) {
174
    		$vendor = $this->prompt("Enter the username/vendor for this repo (e.g. luyadev)");
175
    	}
176
    	
177
    	return $this->cloneRepo($repo, $this->getCloneUrlBasedOnType($repo, $vendor), $this->getFilesystemRepoPath($repo), $vendor);
0 ignored issues
show
Bug introduced by
It seems like $repo can also be of type string; however, luya\dev\RepoController::getCloneUrlBasedOnType() does only seem to accept object<luya\dev\unknown>, 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...
Bug introduced by
It seems like $vendor can also be of type string; however, luya\dev\RepoController::getCloneUrlBasedOnType() does only seem to accept object<luya\dev\unknown>, 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...
178
    }
179
    
180
    private $_gitWrapper;
181
    
182
    /**
183
     * @return \GitWrapper\GitWrapper
184
     */
185
    protected function getGitWrapper()
186
    {
187
    	if ($this->_gitWrapper === null) {
188
    		$this->_gitWrapper = new GitWrapper();
189
    		$this->_gitWrapper->setTimeout(300);
190
    	}
191
    
192
    	return $this->_gitWrapper;
193
    }
194
    
195
    private function summaryItem($repo, $isFork, $exists)
196
    {
197
    	return [$repo, $exists, $isFork];
198
    }
199
    
200
    private function getFilesystemRepoPath($repo)
201
    {
202
    	return 'repos' . DIRECTORY_SEPARATOR . $repo;
203
    }
204
    
205
    private function forkExists($username, $repo)
206
    {
207
    	return (new Curl())->get('https://api.github.com/repos/'.$username.'/'.$repo)->isSuccess();
208
    }
209
    
210
    private function markdown($text, $paragraph = false)
211
    {
212
    	$parser = new Markdown();
213
    
214
    	if ($paragraph) {
215
    		return $parser->parseParagraph($text);
216
    	}
217
    
218
    	return $parser->parse($text);
219
    }
220
    
221
    /**
222
     * Return the url to clone based on config clone type (ssh/https).
223
     * 
224
     * @param unknown $repo
225
     * @param unknown $username
226
     * @return string
227
     */
228
    private function getCloneUrlBasedOnType($repo, $username)
229
    {
230
    	return ($this->getConfig(self::CONFIG_VAR_CLONETYPE) == 'ssh') ? "[email protected]:{$username}/{$repo}.git" : "https://github.com/{$username}/{$repo}.git";
231
    }
232
    
233
    /**
234
     * Clone a repo into the repos folder.
235
     * 
236
     * @param string $repo
237
     * @param string $cloneUrl
238
     * @param string $newRepoHome
239
     * @param string $upstreamUsername The upstream vendor name of the repo if available.
240
     */
241
    private function cloneRepo($repo, $cloneUrl, $newRepoHome, $upstreamUsername)
242
    {
243
    	$this->outputSuccess("{$repo}: cloning ...");
244
    	$this->getGitWrapper()->cloneRepository($cloneUrl, $newRepoHome);
245
    	if (!empty($upstreamUsername)) {
246
    		$this->getGitWrapper()->git('remote add upstream https://github.com/'.$upstreamUsername.'/'.$repo.'.git',  $newRepoHome);
247
    		$this->outputInfo("Configure upstream https://github.com/{$upstreamUsername}/{$repo}.git");
248
    	}
249
    	$this->outputSuccess("{$repo}: ✔ complete");
250
    }
251
}
252