Completed
Push — add/relase-scripts-package ( 9d8a04...097cd4 )
by Yaroslav
68:29 queued 59:27
created

Release   A

Complexity

Total Complexity 23

Size/Duplication

Total Lines 194
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 3

Importance

Changes 0
Metric Value
dl 0
loc 194
rs 10
c 0
b 0
f 0
wmc 23
lcom 1
cbo 3

9 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 3 1
A run() 0 16 2
A handle_package_update() 0 12 2
A is_update_requested() 0 7 1
A handle_dependencies_updates() 0 10 3
A do_dependency_update() 0 23 2
A handle_polar_question() 0 11 5
A is_update_possible() 0 29 3
A get_package_dependencies_to_update() 0 19 4
1
<?php
2
/**
3
 * Set of scripts for Jetpack.
4
 *
5
 * @package automattic/jetpack-scripts
6
 */
7
8
namespace Automattic\Jetpack\Scripts;
9
10
/**
11
 * Package release handler
12
 */
13
class Release {
14
	/**
15
	 * List of update statuses for dependencies
16
	 *
17
	 * @var Array
18
	 */
19
	private $update_statuses = array();
20
21
	/**
22
	 * Sets a logger
23
	 */
24
	public function __construct() {
25
		$this->logger = new Logger();
0 ignored issues
show
Bug introduced by
The property logger does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
26
	}
27
28
	/**
29
	 * Class entry point. Called externally via composer script
30
	 */
31
	public static function run() {
32
		$release = new Release();
33
		Logger::info( 'Doing stuff...' );
34
		$root = Dependency_Tree::generate();
35
		Logger::log( $root );
36
37
		$list = $release->get_package_dependencies_to_update( $root );
38
39
		if ( ! empty( $list ) ) {
40
			$release->handle_dependencies_updates( $list );
41
		}
42
43
		$release->handle_package_update( $root['name'] );
44
45
		Logger::info( 'Done with running!' );
46
	}
47
48
	/**
49
	 * Handles root package update
50
	 *
51
	 * @param String $name package name.
52
	 */
53
	public function handle_package_update( $name ) {
54
		$sh = new Git_Shell_Command( $name );
55
56
		$sh->clone_repository();
57
58
		$tag  = $sh->get_latest_tag();
59
		$diff = $sh->get_diff_between( $tag, 'master' );
60
61
		if ( $this->is_update_requested( $name, $tag, $diff ) ) {
62
			$this->do_dependency_update( $name );
63
		}
64
	}
65
66
	/**
67
	 * Checks if user requested an update
68
	 *
69
	 * @param String $name repo name.
70
	 * @param String $tag latest released version.
71
	 * @param String $diff short diff of unreleased changes.
72
	 */
73
	public function is_update_requested( $name, $tag, $diff ) {
74
		Logger::log( "Package name: $name", 'blue' );
75
		Logger::log( "Latest stable version: $tag", 'blue' );
76
		Logger::log( "Unreleased changes: $diff", 'blue' );
77
78
		return $this->handle_polar_question( 'Do you want to update this package [y/n]? ' );
79
	}
80
81
	/**
82
	 * Loops through provided list of dependencies and tries to update them
83
	 *
84
	 * @param Array $list list of dependencies.
85
	 */
86
	public function handle_dependencies_updates( $list ) {
87
		Logger::info( 'Here is the list of dependencies with some unreleased changes:' );
88
		Logger::log( $list, 1 );
0 ignored issues
show
Documentation introduced by
$list is of type array, 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...
89
90
		foreach ( $list as $dep ) {
91
			if ( $this->is_update_requested( $dep[0], $dep[2], $dep[1] ) ) {
92
				$this->do_dependency_update( $dep[0] );
93
			}
94
		}
95
	}
96
97
	/**
98
	 * Run an actual package update
99
	 *
100
	 * @param String $name package name.
101
	 * @throws \Exception Invalid provided version.
102
	 */
103
	public function do_dependency_update( $name ) {
104
		Logger::info( "Updating dependency: $name" );
105
106
		$prompt = $this->logger->get_colored_string(
107
			'Please provide a version number that should be used for new release in SemVer format (x.x.x): ',
108
			'green'
109
		);
110
111
		$version = readline( $prompt );
112
113
		if ( 3 !== count( explode( '.', $version ) ) ) {
114
			throw new \Exception( "$version is not in a SemVer format" );
115
		}
116
117
		$version = 'v' . $version;
118
		$branch  = "release-$version";
119
120
		$sh = new Git_Shell_Command( $name );
121
		$sh->clone_repository();
122
		$sh->checkout_new_branch( $branch );
123
124
		return true;
125
	}
126
127
	/**
128
	 * Prompts a polar (Y/N) question to a user, and returns a bool
129
	 *
130
	 * @param String $prompt question to ask.
131
	 * @throws \Exception Invalid response.
132
	 */
133
	public function handle_polar_question( $prompt ) {
134
		$in = readline( $this->logger->get_colored_string( $prompt, 'green' ) );
135
136
		if ( 'y' === $in || 'Y' === $in ) {
137
			return true;
138
		} elseif ( 'n' === $in || 'N' === $in ) {
139
			return false;
140
		} else {
141
			throw new \Exception( 'Invalid response. Expected Y/y/N/n' );
142
		}
143
	}
144
145
146
	/**
147
	 * Checks wether a package have some unreleased changes
148
	 *
149
	 * @param String $name repository name.
150
	 */
151
	public function is_update_possible( $name ) {
152
		/**
153
		 * Inside $name repository:
154
		 * - get latest tag: `git describe --abbrev=0`
155
		 * - compare tag against master: `git diff $tag master --shortstat`
156
		 * - if output is not empty - there is a difference
157
		 */
158
159
		if ( array_key_exists( $name, $this->update_statuses ) ) {
160
			return $this->update_statuses[ $name ];
161
		}
162
163
		$sh = new Git_Shell_Command( $name );
164
		$sh->clone_repository();
165
		$tag = $sh->get_latest_tag();
166
		$out = $sh->get_diff_between( $tag, 'master' );
167
168
		$result = array(
169
			'status' => true,
170
			'diff'   => $out,
171
			'tag'    => $tag,
172
		);
173
		if ( 0 === strlen( $out ) ) {
174
			$result['status'] = false;
175
		}
176
177
		$this->update_statuses[ $name ] = $result;
178
		return $result;
179
	}
180
181
	/**
182
	 * Walks through the dependency tree and builds a list of deps that needs to be updated
183
	 * This list is build in order from branch up to root, meaning it is the order that could be used for updating the whole tree in single run
184
	 *
185
	 * @param Array $object tree node.
186
	 */
187
	public function get_package_dependencies_to_update( $object ) {
188
		$object_requires = $object['requires'];
189
		$deps_to_update  = array();
190
191
		foreach ( $object_requires as $dependency ) {
192
			// Check if we need to update this package.
193
			$update = $this->is_update_possible( $dependency['name'] );
194
			if ( $update['status'] ) {
195
				array_unshift( $deps_to_update, array( $dependency['name'], $update['diff'], $update['tag'] ) );
196
				if ( array_key_exists( 'requires', $dependency ) ) {
197
					// $dependency have dependencies, lets recursively go through them too.
198
					$deps = $this->get_package_dependencies_to_update( $dependency );
199
					array_merge( $deps, $deps_to_update );
200
				}
201
			}
202
		}
203
204
		return $deps_to_update;
205
	}
206
}
207