| 
									
										
										
										
											2020-08-26 13:49:43 -07:00
										 |  |  | /** | 
					
						
							|  |  |  |  * @license | 
					
						
							|  |  |  |  * Copyright Google LLC All Rights Reserved. | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * Use of this source code is governed by an MIT-style license that can be | 
					
						
							|  |  |  |  * found in the LICENSE file at https://angular.io/license
 | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-09-11 12:13:36 -07:00
										 |  |  | import {existsSync, readFileSync} from 'fs'; | 
					
						
							| 
									
										
										
										
											2020-08-26 13:49:43 -07:00
										 |  |  | import * as multimatch from 'multimatch'; | 
					
						
							|  |  |  | import {join} from 'path'; | 
					
						
							|  |  |  | import {parse as parseYaml} from 'yaml'; | 
					
						
							|  |  |  | import {getRepoBaseDir} from '../../utils/config'; | 
					
						
							| 
									
										
										
										
											2020-09-14 11:24:15 -07:00
										 |  |  | import {bold, debug, error, info} from '../../utils/console'; | 
					
						
							| 
									
										
										
										
											2020-08-26 13:49:43 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-09-14 11:24:15 -07:00
										 |  |  | import {BaseModule} from './base'; | 
					
						
							| 
									
										
										
										
											2020-08-26 13:49:43 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-09-14 11:24:15 -07:00
										 |  |  | /** Information expressing the difference between the master and g3 branches */ | 
					
						
							|  |  |  | export interface G3StatsData { | 
					
						
							|  |  |  |   insertions: number; | 
					
						
							|  |  |  |   deletions: number; | 
					
						
							|  |  |  |   files: number; | 
					
						
							|  |  |  |   commits: number; | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2020-08-26 13:49:43 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-09-14 11:24:15 -07:00
										 |  |  | export class G3Module extends BaseModule<G3StatsData|void> { | 
					
						
							|  |  |  |   async retrieveData() { | 
					
						
							|  |  |  |     const toCopyToG3 = this.getG3FileIncludeAndExcludeLists(); | 
					
						
							|  |  |  |     const latestSha = this.getLatestShas(); | 
					
						
							| 
									
										
										
										
											2020-08-26 13:49:43 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-09-14 11:24:15 -07:00
										 |  |  |     if (toCopyToG3 === null || latestSha === null) { | 
					
						
							|  |  |  |       return; | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2020-08-26 13:49:43 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-09-14 11:24:15 -07:00
										 |  |  |     return this.getDiffStats( | 
					
						
							|  |  |  |         latestSha.g3, latestSha.master, toCopyToG3.include, toCopyToG3.exclude); | 
					
						
							| 
									
										
										
										
											2020-08-26 13:49:43 -07:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-09-14 11:24:15 -07:00
										 |  |  |   async printToTerminal() { | 
					
						
							|  |  |  |     const stats = await this.data; | 
					
						
							|  |  |  |     if (!stats) { | 
					
						
							|  |  |  |       return; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     info.group(bold('g3 branch check')); | 
					
						
							|  |  |  |     if (stats.files === 0) { | 
					
						
							|  |  |  |       info(`${stats.commits} commits between g3 and master`); | 
					
						
							|  |  |  |       info('✅  No sync is needed at this time'); | 
					
						
							|  |  |  |     } else { | 
					
						
							|  |  |  |       info( | 
					
						
							|  |  |  |           `${stats.files} files changed, ${stats.insertions} insertions(+), ${stats.deletions} ` + | 
					
						
							|  |  |  |           `deletions(-) from ${stats.commits} commits will be included in the next sync`); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     info.groupEnd(); | 
					
						
							|  |  |  |     info(); | 
					
						
							|  |  |  |   } | 
					
						
							| 
									
										
										
										
											2020-08-26 13:49:43 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-10-06 10:13:54 -07:00
										 |  |  |   /** Fetch and retrieve the latest sha for a specific branch. */ | 
					
						
							| 
									
										
										
										
											2020-09-14 11:24:15 -07:00
										 |  |  |   private getShaForBranchLatest(branch: string) { | 
					
						
							|  |  |  |     const {owner, name} = this.git.remoteConfig; | 
					
						
							| 
									
										
										
										
											2020-10-06 10:13:54 -07:00
										 |  |  |     /** The result fo the fetch command. */ | 
					
						
							| 
									
										
										
										
											2020-09-14 11:24:15 -07:00
										 |  |  |     const fetchResult = | 
					
						
							|  |  |  |         this.git.runGraceful(['fetch', '-q', `https://github.com/${owner}/${name}.git`, branch]); | 
					
						
							| 
									
										
										
										
											2020-10-06 10:13:54 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  |     if (fetchResult.status !== 0 && | 
					
						
							|  |  |  |         fetchResult.stderr.includes(`couldn't find remote ref ${branch}`)) { | 
					
						
							|  |  |  |       debug(`No '${branch}' branch exists on upstream, skipping.`); | 
					
						
							| 
									
										
										
										
											2020-09-14 11:24:15 -07:00
										 |  |  |       return null; | 
					
						
							| 
									
										
										
										
											2020-10-06 10:13:54 -07:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2020-09-14 11:24:15 -07:00
										 |  |  |     return this.git.runGraceful(['rev-parse', 'FETCH_HEAD']).stdout.trim(); | 
					
						
							| 
									
										
										
										
											2020-10-06 10:13:54 -07:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-08-26 13:49:43 -07:00
										 |  |  |   /** | 
					
						
							|  |  |  |    * Get git diff stats between master and g3, for all files and filtered to only g3 affecting | 
					
						
							|  |  |  |    * files. | 
					
						
							|  |  |  |    */ | 
					
						
							| 
									
										
										
										
											2020-09-14 11:24:15 -07:00
										 |  |  |   private getDiffStats( | 
					
						
							|  |  |  |       g3Ref: string, masterRef: string, includeFiles: string[], excludeFiles: string[]) { | 
					
						
							| 
									
										
										
										
											2020-08-26 13:49:43 -07:00
										 |  |  |     /** The diff stats to be returned. */ | 
					
						
							|  |  |  |     const stats = { | 
					
						
							|  |  |  |       insertions: 0, | 
					
						
							|  |  |  |       deletions: 0, | 
					
						
							|  |  |  |       files: 0, | 
					
						
							|  |  |  |       commits: 0, | 
					
						
							|  |  |  |     }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // Determine the number of commits between master and g3 refs. */
 | 
					
						
							| 
									
										
										
										
											2020-09-14 11:24:15 -07:00
										 |  |  |     stats.commits = | 
					
						
							|  |  |  |         parseInt(this.git.run(['rev-list', '--count', `${g3Ref}..${masterRef}`]).stdout, 10); | 
					
						
							| 
									
										
										
										
											2020-08-26 13:49:43 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  |     // Get the numstat information between master and g3
 | 
					
						
							| 
									
										
										
										
											2020-09-14 11:24:15 -07:00
										 |  |  |     this.git.run(['diff', `${g3Ref}...${masterRef}`, '--numstat']) | 
					
						
							| 
									
										
										
										
											2020-08-26 13:49:43 -07:00
										 |  |  |         .stdout | 
					
						
							|  |  |  |         // Remove the extra space after git's output.
 | 
					
						
							|  |  |  |         .trim() | 
					
						
							|  |  |  |         // Split each line of git output into array
 | 
					
						
							|  |  |  |         .split('\n') | 
					
						
							|  |  |  |         // Split each line from the git output into components parts: insertions,
 | 
					
						
							|  |  |  |         // deletions and file name respectively
 | 
					
						
							| 
									
										
										
										
											2020-09-14 11:24:15 -07:00
										 |  |  |         .map(line => line.trim().split('\t')) | 
					
						
							| 
									
										
										
										
											2020-08-26 13:49:43 -07:00
										 |  |  |         // Parse number value from the insertions and deletions values
 | 
					
						
							|  |  |  |         // Example raw line input:
 | 
					
						
							|  |  |  |         //   10\t5\tsrc/file/name.ts
 | 
					
						
							|  |  |  |         .map(line => [Number(line[0]), Number(line[1]), line[2]] as [number, number, string]) | 
					
						
							|  |  |  |         // Add each line's value to the diff stats, and conditionally to the g3
 | 
					
						
							|  |  |  |         // stats as well if the file name is included in the files synced to g3.
 | 
					
						
							|  |  |  |         .forEach(([insertions, deletions, fileName]) => { | 
					
						
							| 
									
										
										
										
											2020-09-14 11:24:15 -07:00
										 |  |  |           if (this.checkMatchAgainstIncludeAndExclude(fileName, includeFiles, excludeFiles)) { | 
					
						
							| 
									
										
										
										
											2020-08-26 13:49:43 -07:00
										 |  |  |             stats.insertions += insertions; | 
					
						
							|  |  |  |             stats.deletions += deletions; | 
					
						
							|  |  |  |             stats.files += 1; | 
					
						
							|  |  |  |           } | 
					
						
							|  |  |  |         }); | 
					
						
							|  |  |  |     return stats; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |   /** Determine whether the file name passes both include and exclude checks. */ | 
					
						
							| 
									
										
										
										
											2020-09-14 11:24:15 -07:00
										 |  |  |   private checkMatchAgainstIncludeAndExclude(file: string, includes: string[], excludes: string[]) { | 
					
						
							| 
									
										
										
										
											2020-10-01 16:06:56 -07:00
										 |  |  |     return ( | 
					
						
							|  |  |  |         multimatch.call(undefined, file, includes).length >= 1 && | 
					
						
							|  |  |  |         multimatch.call(undefined, file, excludes).length === 0); | 
					
						
							| 
									
										
										
										
											2020-08-26 13:49:43 -07:00
										 |  |  |   } | 
					
						
							| 
									
										
										
										
											2020-09-14 11:24:15 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   private getG3FileIncludeAndExcludeLists() { | 
					
						
							|  |  |  |     const angularRobotFilePath = join(getRepoBaseDir(), '.github/angular-robot.yml'); | 
					
						
							|  |  |  |     if (!existsSync(angularRobotFilePath)) { | 
					
						
							|  |  |  |       debug('No angular robot configuration file exists, skipping.'); | 
					
						
							|  |  |  |       return null; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     /** The configuration defined for the angular robot. */ | 
					
						
							|  |  |  |     const robotConfig = parseYaml(readFileSync(angularRobotFilePath).toString()); | 
					
						
							|  |  |  |     /** The files to be included in the g3 sync. */ | 
					
						
							|  |  |  |     const include: string[] = robotConfig?.merge?.g3Status?.include || []; | 
					
						
							|  |  |  |     /** The files to be expected in the g3 sync. */ | 
					
						
							|  |  |  |     const exclude: string[] = robotConfig?.merge?.g3Status?.exclude || []; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (include.length === 0 && exclude.length === 0) { | 
					
						
							|  |  |  |       debug('No g3Status include or exclude lists are defined in the angular robot configuration'); | 
					
						
							|  |  |  |       return null; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return {include, exclude}; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   private getLatestShas() { | 
					
						
							|  |  |  |     /** The latest sha for the g3 branch. */ | 
					
						
							|  |  |  |     const g3 = this.getShaForBranchLatest('g3'); | 
					
						
							|  |  |  |     /** The latest sha for the master branch. */ | 
					
						
							|  |  |  |     const master = this.getShaForBranchLatest('master'); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (g3 === null || master === null) { | 
					
						
							|  |  |  |       debug('Either the g3 or master was unable to be retrieved'); | 
					
						
							|  |  |  |       return null; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return {g3, master}; | 
					
						
							|  |  |  |   } | 
					
						
							| 
									
										
										
										
											2020-08-26 13:49:43 -07:00
										 |  |  | } |