Completed
Push — add/relase-scripts-package ( 6d2bc4 )
by Yaroslav
29:20 queued 22:51
created

Release::get_package_dependencies_to_update()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 19

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
nc 4
nop 1
dl 0
loc 19
rs 9.6333
c 0
b 0
f 0
1
<?php
2
/**
3
 * Set of scripts for Jetpack.
4
 *
5
 * @package automattic/jetpack-scripts
6
 */
7
8
namespace Automattic\Jetpack\Scripts;
9
10
// phpcs:disable WordPress.PHP.DevelopmentFunctions.error_log_error_log
11
12
/**
13
 * Package release handler
14
 */
15
class Release {
16
	/**
17
	 * List of update statuses for dependencies
18
	 *
19
	 * @var Array
20
	 */
21
	private $update_statuses = array();
22
23
	/**
24
	 * Class entry point. Called externally via composer script
25
	 */
26
	public static function run() {
27
		$release = new Release();
28
		error_log( 'Doing stuff...' );
29
		$root = Dependency_Tree::generate();
30
		error_log( $root );
31
32
		$list = $release->get_package_dependencies_to_update( $root );
33
34
		if ( ! empty( $list ) ) {
35
			$release->handle_dependencies_updates( $list );
36
		}
37
38
		$release->handle_package_update( $root['name'] );
39
40
		error_log( 'Done with running!' );
41
	}
42
43
	/**
44
	 * Handles root package update
45
	 *
46
	 * @param String $name package name.
47
	 */
48
	public function handle_package_update( $name ) {
49
		$sh = new Git_Shell_Command( $name );
50
51
		$sh->clone_repository();
52
53
		$tag  = $sh->get_latest_tag();
54
		$diff = $sh->get_diff_between( $tag, 'master' );
55
56
		if ( $this->is_update_requested( $name, $tag, $diff ) ) {
57
			$this->do_dependency_update( $name );
58
		}
59
	}
60
61
	/**
62
	 * Checks if user requested an update
63
	 *
64
	 * @param String $name repo name.
65
	 * @param String $tag latest released version.
66
	 * @param String $diff short diff of unreleased changes.
67
	 */
68
	public function is_update_requested( $name, $tag, $diff ) {
69
		error_log( "Updating $name" );
70
		error_log( "Latest stable version: $tag" );
71
		error_log( "Unreleased changes: $diff" );
72
73
		return $this->handle_polar_question( "Do you want to update $name [y/n]? " );
74
	}
75
76
	/**
77
	 * Loops through provided list of dependencies and tries to update them
78
	 *
79
	 * @param Array $list list of dependencies.
80
	 */
81
	public function handle_dependencies_updates( $list ) {
82
		error_log( 'Here is the list of dependencies with some unreleased changes:' );
83
		error_log( $list );
84
85
		foreach ( $list as $dep ) {
86
			if ( $this->is_update_requested( $dep[0], $dep[2], $dep[1] ) ) {
87
				$this->do_dependency_update( $dep[0] );
88
			}
89
		}
90
	}
91
92
	/**
93
	 * Run an actual package update
94
	 *
95
	 * @param String $name package name.
96
	 * @throws \Exception Invalid provided version.
97
	 */
98
	public function do_dependency_update( $name ) {
99
		error_log( "Updating dependency: $name" );
100
101
		$version = readline( 'Please provide a version number that should be used for new release in SemVer format (x.x.x): ' );
102
103
		if ( 3 !== count( explode( '.', $version ) ) ) {
104
			throw new \Exception( "$version is not in a SemVer format" );
105
		}
106
107
		$version = 'v' . $version;
108
		$branch  = "release-$version";
109
110
		$sh = new Git_Shell_Command( $name );
111
		$sh->clone_repository();
112
		$sh->checkout_new_branch( $branch );
113
114
		return true;
115
	}
116
117
	/**
118
	 * Prompts a polar (Y/N) question to a user, and returns a bool
119
	 *
120
	 * @param String $prompt question to ask.
121
	 * @throws \Exception Invalid response.
122
	 */
123
	public function handle_polar_question( $prompt ) {
124
		$in = readline( $prompt );
125
126
		if ( 'y' === $in || 'Y' === $in ) {
127
			return true;
128
		} elseif ( 'n' === $in || 'N' === $in ) {
129
			return false;
130
		} else {
131
			throw new \Exception( 'Invalid response. Expected Y/y/N/n' );
132
		}
133
	}
134
135
136
	/**
137
	 * Checks wether a package have some unreleased changes
138
	 *
139
	 * @param String $name repository name.
140
	 */
141
	public function is_update_possible( $name ) {
142
		/**
143
		 * Inside $name repository:
144
		 * - get latest tag: `git describe --abbrev=0`
145
		 * - compare tag against master: `git diff $tag master --shortstat`
146
		 * - if output is not empty - there is a difference
147
		 */
148
149
		if ( array_key_exists( $name, $this->update_statuses ) ) {
150
			return $this->update_statuses[ $name ];
151
		}
152
153
		$sh = new Git_Shell_Command( $name );
154
		$sh->clone_repository();
155
		$tag = $sh->get_latest_tag();
156
		$out = $sh->get_diff_between( $tag, 'master' );
157
158
		$result = array(
159
			'status' => true,
160
			'diff'   => $out,
161
			'tag'    => $tag,
162
		);
163
		if ( 0 === strlen( $out ) ) {
164
			$result['status'] = false;
165
		}
166
167
		$this->update_statuses[ $name ] = $result;
168
		return $result;
169
	}
170
171
	/**
172
	 * Walks through the dependency tree and builds a list of deps that needs to be updated
173
	 * 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
174
	 *
175
	 * @param Array $object tree node.
176
	 */
177
	public function get_package_dependencies_to_update( $object ) {
178
		$object_requires = $object['requires'];
179
		$deps_to_update  = array();
180
181
		foreach ( $object_requires as $dependency ) {
182
			// Check if we need to update this package.
183
			$update = $this->is_update_possible( $dependency['name'] );
184
			if ( $update['status'] ) {
185
				array_unshift( $deps_to_update, array( $dependency['name'], $update['diff'], $update['tag'] ) );
186
				if ( array_key_exists( 'requires', $dependency ) ) {
187
					// $dependency have dependencies, lets recursively go through them too.
188
					$deps = $this->get_package_dependencies_to_update( $dependency );
189
					array_merge( $deps, $deps_to_update );
190
				}
191
			}
192
		}
193
194
		return $deps_to_update;
195
	}
196
}
197