11import { execSync } from "child_process" ;
2- import { existsSync } from "fs" ;
32import * as micromatch from "micromatch" ;
4- import { resolve } from "path" ;
53
64import { Options , Path } from "../types" ;
75import { buildDebugger , withDuration } from "../../utils" ;
6+ import { resolve } from "path" ;
7+ import { existsSync } from "fs" ;
88
9- type ParsedLine = { relativePath : string ; commitCount : string } ;
109const internal = { debug : buildDebugger ( "churn" ) } ;
1110const PER_LINE = "\n" ;
1211
@@ -16,67 +15,19 @@ export default {
1615} ;
1716
1817async function compute ( options : Options ) : Promise < Map < Path , number > > {
19- assertGitIsInstalled ( ) ;
20- assertIsGitRootDirectory ( options . directory ) ;
21-
2218 const gitLogCommand = buildGitLogCommand ( options ) ;
23- const rawStringOfAllChurns = executeGitLogCommand ( gitLogCommand ) ;
24- const arrayOfAllChurns = computeChurnsPerFiles (
25- rawStringOfAllChurns ,
26- options . directory
27- ) ;
28- const mapOfAllChurns = createMapOfChurnsPerFile ( arrayOfAllChurns ) ;
29- return applyUserFilters ( mapOfAllChurns , options . filter ) ;
30- }
31-
32- function applyUserFilters (
33- allChurns : Map < Path , number > ,
34- filter : string [ ] | undefined
35- ) : Map < Path , number > {
36- const filteredChurns : Map < Path , number > = new Map ( ) ;
37- allChurns . forEach ( ( churn : number , path : Path ) => {
38- const patchIsAMatch =
39- filter && filter . length > 0
40- ? filter . every ( ( f ) => micromatch . isMatch ( path , f ) )
41- : true ;
42-
43- if ( patchIsAMatch ) filteredChurns . set ( path , churn ) ;
44- } ) ;
45- return filteredChurns ;
46- }
47-
48- function createMapOfChurnsPerFile (
49- numberOfTimesFilesChanged : ParsedLine [ ]
50- ) : Map < Path , number > {
51- return numberOfTimesFilesChanged . reduce (
52- ( map : Map < Path , number > , { relativePath, commitCount } : ParsedLine ) => {
53- const path : Path = relativePath ;
54- const churn = parseInt ( commitCount , 10 ) ;
55- map . set ( path , churn ) ;
56- return map ;
57- } ,
58- new Map ( )
19+ const singleStringWithAllChurns = executeGitLogCommand ( gitLogCommand ) ;
20+ return computeChurnsPerFiles (
21+ singleStringWithAllChurns ,
22+ options . directory ,
23+ options . filter
5924 ) ;
6025}
6126
6227function executeGitLogCommand ( gitLogCommand : string ) : string {
6328 return execSync ( gitLogCommand , { encoding : "utf8" , maxBuffer : 32_000_000 } ) ;
6429}
6530
66- function assertGitIsInstalled ( ) : void {
67- try {
68- execSync ( "which git" ) ;
69- } catch ( error ) {
70- throw new Error ( "Program 'git' must be installed" ) ;
71- }
72- }
73-
74- function assertIsGitRootDirectory ( directory : string ) : void {
75- if ( ! existsSync ( `${ directory } /.git` ) ) {
76- throw new Error ( `Argument 'dir' must be the git root directory.` ) ;
77- }
78- }
79-
8031function buildGitLogCommand ( options : Options ) : string {
8132 const isWindows = process . platform === "win32" ;
8233
@@ -102,30 +53,42 @@ function buildGitLogCommand(options: Options): string {
10253
10354function computeChurnsPerFiles (
10455 gitLogOutput : string ,
105- directory : string
106- ) : ParsedLine [ ] {
56+ directory : string ,
57+ filters : string [ ] | undefined
58+ ) : Map < Path , number > {
10759 const changedFiles = gitLogOutput
10860 . split ( PER_LINE )
10961 . filter ( ( line ) => line !== "" )
11062 . sort ( ) ;
11163
112- const changedFilesCount = changedFiles . reduce (
113- ( fileAndTimeChanged : { [ fileName : string ] : number } , fileName ) => {
114- fileAndTimeChanged [ fileName ] = ( fileAndTimeChanged [ fileName ] || 0 ) + 1 ;
115- return fileAndTimeChanged ;
116- } ,
117- { }
118- ) ;
64+ return changedFiles . reduce ( ( map : Map < Path , number > , path ) => {
65+ applyFiltersAndExcludeObsoletePath ( path , map ) ;
66+ return map ;
67+ } , new Map ( ) ) ;
68+
69+ function applyFiltersAndExcludeObsoletePath (
70+ path : string ,
71+ map : Map < Path , number >
72+ ) {
73+ if ( ! filters || ! filters . length ) {
74+ if ( pathStillExists ( path ) ) {
75+ addOrIncrement ( map , path ) ;
76+ }
77+ } else {
78+ const pathHasAMatch = filters . every ( ( f ) => micromatch . isMatch ( path , f ) ) ;
79+ if ( pathHasAMatch ) {
80+ if ( pathStillExists ( path ) ) {
81+ addOrIncrement ( map , path ) ;
82+ }
83+ }
84+ }
85+ }
11986
120- return Object . keys ( changedFilesCount )
121- . map (
122- ( changedFileName ) =>
123- ( {
124- relativePath : changedFileName ,
125- commitCount : changedFilesCount [ changedFileName ] . toString ( ) ,
126- } as ParsedLine )
127- )
128- . filter ( ( parsedLine : ParsedLine ) => {
129- return existsSync ( resolve ( directory , parsedLine . relativePath ) ) ;
130- } ) ;
87+ function addOrIncrement ( map : Map < Path , number > , path : string ) {
88+ map . set ( path , ( map . get ( path ) ?? 0 ) + 1 ) ;
89+ }
90+
91+ function pathStillExists ( fileName : string ) {
92+ return existsSync ( resolve ( directory , fileName ) ) ;
93+ }
13194}
0 commit comments