• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

moosetechnology / GitProjectHealth / 10452237749

19 Aug 2024 11:10AM UTC coverage: 18.86% (-14.8%) from 33.667%
10452237749

Pull #31

github

web-flow
Merge 2cf167214 into 48c9c9c78
Pull Request #31: fix: error if there are no commits in averageTimeBetweenCommits method

163 of 1071 new or added lines in 15 files covered. (15.22%)

856 existing lines in 12 files now uncovered.

1585 of 8404 relevant lines covered (18.86%)

0.19 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/src/GitLabHealth-Model-Analysis/GitAnalyzer.class.st
1
Class {
2
        #name : #GitAnalyzer,
3
        #superclass : #Object,
4
        #instVars : [
5
                'glModel',
6
                'fromCommit',
7
                'glhImporter',
8
                'onProject',
9
                'maxChildCommits'
10
        ],
11
        #category : 'GitLabHealth-Model-Analysis'
12
}
13

14
{ #category : #analyze }
UNCOV
15
GitAnalyzer >> analyseChurn [
×
UNCOV
16

×
UNCOV
17
        | commitFiles totalContribution childCommits access |
×
UNCOV
18
        access := ('' join: {
×
UNCOV
19
                                   'churn'.
×
UNCOV
20
                                   maxChildCommits }) asSymbol.
×
UNCOV
21

×
UNCOV
22
        ^ fromCommit cacheAt: access ifAbsentPut: [
×
UNCOV
23
                  ('GitAnalyzer, analyse chrun onProject: ' , onProject printString)
×
UNCOV
24
                          recordInfo.
×
UNCOV
25
                  childCommits := Set new.
×
UNCOV
26
                  totalContribution := self
×
UNCOV
27
                                               visitChildCommits: fromCommit childCommits
×
UNCOV
28
                                               toStoreThemIn: childCommits
×
UNCOV
29
                                               upto: self maxChildCommits.
×
UNCOV
30
                  totalContribution add: fromCommit. 
×
UNCOV
31
                  totalContribution := totalContribution sum: [ :commit | "nil if merge request commit"
×
UNCOV
32
                                               commit additions ifNil: [ 0 ] ].
×
UNCOV
33
                  commitFiles := self
×
UNCOV
34
                                         impactedFilesInFollowUpCommitsOf: fromCommit
×
UNCOV
35
                                         withMaxCommits: self maxChildCommits.
×
UNCOV
36
                        "a churned line is a line added on top of an already added line"
×
UNCOV
37
                  (self computeChurnOnFiles: commitFiles)
×
UNCOV
38
                          at: #totalContribution ifAbsentPut: totalContribution;
×
UNCOV
39
                          yourself ]
×
UNCOV
40
]
×
41

42
{ #category : #analyze }
UNCOV
43
GitAnalyzer >> analyseCommentContribution [
×
UNCOV
44

×
UNCOV
45
        | numberOfComments |
×
UNCOV
46
        ('GitAnalyzer, analyse comment contributions onProject: '
×
UNCOV
47
         , onProject printString) recordInfo.
×
UNCOV
48
        numberOfComments := 0.
×
UNCOV
49
        fromCommit diffs do: [ :diff |
×
UNCOV
50
                diff diffRanges do: [ :range |
×
UNCOV
51
                        range changes do: [ :change |
×
UNCOV
52
                                ((change isKindOf: GLPHEAddition) and: [
×
UNCOV
53
                                         (change sourceCode withoutPrefix: '+') trimLeft
×
UNCOV
54
                                                 beginsWithAnyOf: { '#'. '//'. '/*'. '*'. '*/' } ]) ifTrue: [
×
UNCOV
55
                                        numberOfComments := numberOfComments + 1 ] ] ] ].
×
UNCOV
56
        ^ numberOfComments
×
UNCOV
57
]
×
58

59
{ #category : #commit }
60
GitAnalyzer >> analyseCommitContribution [
×
61
        
×
62
        
×
63
        ('GitAnalyzer, analyse commit contribution of: ', fromCommit printString )
×
64
                recordInfo.
×
65
        
×
66
        ^ { (#addition -> fromCommit additions).
×
67
          (#deletion -> fromCommit deletions). } asDictionary 
×
68
]
×
69

70
{ #category : #analyze }
71
GitAnalyzer >> analyseCommitFrequencyFromCommits: initialCommits [
×
72

×
73
        | commits response |
×
74
        ('GitAnalyzer, analyse commit Frequency on: ' , onProject printString)
×
75
                recordInfo.
×
76

×
77
        response := {
×
78
                            (#numberOfCommit -> nil).
×
79
                            (#frequency -> nil) } asDictionary.
×
80

×
81

×
82
        commits := self arrangeCommitsByDate: initialCommits.
×
83
        commits := (commits associations sortAscending: [ :entry |
×
84
                            entry key asDate ]) asOrderedDictionary.
×
85

×
86
        ^ commits
×
87
]
×
88

89
{ #category : #analyze }
UNCOV
90
GitAnalyzer >> analyseCommitFrequencySince: since until: until [ 
×
UNCOV
91

×
UNCOV
92
        | commits response |
×
UNCOV
93
        
×
UNCOV
94
        ('GitAnalyzer, analyse commit Frequency on: ', onProject printString )
×
UNCOV
95
                recordInfo.
×
UNCOV
96
        
×
UNCOV
97
        response := {
×
UNCOV
98
                            (#numberOfCommit -> nil).
×
UNCOV
99
                            (#frequency -> nil) } asDictionary.
×
UNCOV
100

×
UNCOV
101
        commits := glhImporter
×
UNCOV
102
                           importCommitsOProject: onProject
×
UNCOV
103
                           since: since
×
UNCOV
104
                           until: until.
×
UNCOV
105

×
UNCOV
106
        commits := self arrangeCommitsByDate: commits.
×
UNCOV
107
        commits := (commits associations sortAscending: [ :entry |
×
UNCOV
108
                            entry key asDate ]) asOrderedDictionary.
×
UNCOV
109

×
UNCOV
110
        ^ commits
×
UNCOV
111
]
×
112

113
{ #category : #analyze }
UNCOV
114
GitAnalyzer >> analyseDelayUntilFirstChurn [
×
UNCOV
115
        "return the first commit that modify the same lines of code as the fromCommit"
×
UNCOV
116

×
UNCOV
117
        | churn res access|
×
UNCOV
118
        
×
UNCOV
119
        access := ('' join: {
×
UNCOV
120
                                   'amandment'.
×
UNCOV
121
                                   maxChildCommits }) asSymbol.
×
UNCOV
122
        
×
UNCOV
123
        ^ fromCommit cacheAt: access ifPresent: [ :v | v ] ifAbsentPut: [
×
UNCOV
124
        
×
UNCOV
125
        ('GitAnalyzer, analyse amandment onProject: ', onProject printString )
×
UNCOV
126
                recordInfo.
×
UNCOV
127
        
×
UNCOV
128
        churn := self analyseChurn.
×
UNCOV
129

×
UNCOV
130
        res := self firstAmandmentFromChrun: churn.
×
UNCOV
131
         res]
×
UNCOV
132
]
×
133

134
{ #category : #analyze }
UNCOV
135
GitAnalyzer >> analyseMergeResquestValidation: aGLHPMergeRequest [
×
UNCOV
136

×
UNCOV
137
        | creationDate mergedDate response |
×
UNCOV
138
        ('GitAnalyzer, analyse merge request delay of: '
×
UNCOV
139
         , aGLHPMergeRequest printString) recordInfo.
×
UNCOV
140

×
UNCOV
141
        ^ aGLHPMergeRequest
×
UNCOV
142
                  cacheAt: #mergeRequestValidation
×
UNCOV
143
                  ifPresent: [ :v | v ]
×
UNCOV
144
                  ifAbsentPut: [
×
UNCOV
145
                          response := {
×
UNCOV
146
                                              (#id_merge_resquest -> aGLHPMergeRequest iid).
×
UNCOV
147
                                              (#id_merge_commit -> nil).
×
UNCOV
148
                                              (#created_at -> aGLHPMergeRequest created_at).
×
UNCOV
149
                                              (#merged_at -> nil).
×
UNCOV
150
                                              (#duration -> nil).
×
UNCOV
151
                                              (#status -> aGLHPMergeRequest merge_status) }
×
UNCOV
152
                                              asDictionary.
×
UNCOV
153

×
UNCOV
154
                          creationDate := aGLHPMergeRequest created_at asDateAndTime.
×
UNCOV
155

×
UNCOV
156
                          mergedDate := aGLHPMergeRequest merged_at ifNil: [ ^ response ].
×
UNCOV
157

×
UNCOV
158
                          response
×
UNCOV
159
                                  at: #duration put: mergedDate - creationDate;
×
UNCOV
160
                                  at: #id_merge_commit put: aGLHPMergeRequest merge_commit_sha;
×
UNCOV
161
                                  at: #merged_at put: aGLHPMergeRequest merged_at;
×
UNCOV
162
                                  yourself ]
×
UNCOV
163
]
×
164

165
{ #category : #filter }
UNCOV
166
GitAnalyzer >> arrangeCommitsByDate: commits [
×
UNCOV
167

×
UNCOV
168
        | date2commits |
×
UNCOV
169
        date2commits := Dictionary new.
×
UNCOV
170

×
UNCOV
171
        commits do: [ :commit |
×
UNCOV
172
                | date |
×
UNCOV
173
                date := commit created_at asDate.
×
UNCOV
174
                date2commits
×
UNCOV
175
                        at: date printString
×
UNCOV
176
                        ifPresent: [ :v | v add: commit ]
×
UNCOV
177
                        ifAbsentPut: [
×
UNCOV
178
                                OrderedCollection new
×
UNCOV
179
                                        add: commit;
×
UNCOV
180
                                        yourself ] ].
×
UNCOV
181
        ^ date2commits
×
UNCOV
182
]
×
183

184
{ #category : #churn }
UNCOV
185
GitAnalyzer >> computeChurnOnFiles: aCollection [
×
UNCOV
186

×
UNCOV
187
        | changesDic perFileChanges churns initialAuthor followingAuthors|
×
UNCOV
188
        "1 -> (a GLPHEChange -> NumberOfChurnDetected)"
×
UNCOV
189
        changesDic := Dictionary new.
×
UNCOV
190
        initialAuthor := fromCommit commitCreator. 
×
UNCOV
191
        
×
UNCOV
192

×
UNCOV
193
        perFileChanges := aCollection associations collect: [ :assoc |
×
UNCOV
194
                                  assoc key
×
UNCOV
195
                                  -> (self computeSpecificChurnOf: assoc value) ].
×
UNCOV
196

×
UNCOV
197

×
UNCOV
198
        churns := perFileChanges collect: [ :assoc |
×
UNCOV
199
                          | churnData file aLineOfChanges |
×
UNCOV
200
                          file := assoc key.
×
UNCOV
201
                          aLineOfChanges := assoc value.
×
UNCOV
202
                          churnData := aLineOfChanges values ifEmpty: [  nil ] ifNotEmpty: [
×
UNCOV
203
                                  | churnedContribution churnSpecificFromCommit churnOfAuthorOnly |
×
UNCOV
204
                                
×
UNCOV
205
                                  "the total churn on any LoC affected"
×
UNCOV
206
                                  churnedContribution := aLineOfChanges select: [ :a |
×
UNCOV
207
                                                                 a value  > 1 ].
×
UNCOV
208

×
UNCOV
209
                                  "the churn that coccurs specifically on the loc introduced by the initial commit"
×
UNCOV
210
                                  churnSpecificFromCommit := churnedContribution select: [ :a |
×
UNCOV
211
                                                                     (a key collect: [ :loc |
×
UNCOV
212
                                                                              loc diffRange diff commit ])
×
UNCOV
213
                                                                             includes: fromCommit ].
×
UNCOV
214

×
UNCOV
215
                                                "the churn made the author of the initial commits "
×
UNCOV
216
                                  churnOfAuthorOnly := churnSpecificFromCommit select: [ :a |
×
UNCOV
217
                                                                     (((a key collect: [ :loc |
×
UNCOV
218
                                                                              loc diffRange diff commit commitCreator ]) asSet) difference: {initialAuthor} asSet ) isEmpty
×
UNCOV
219
                                                                             ].
×
UNCOV
220
                                                
×
UNCOV
221

×
UNCOV
222
                                  {
×
UNCOV
223
                                                   (#churnFromInitialCommitLines
×
UNCOV
224
                                                    ->
×
UNCOV
225
                                                            ((churnSpecificFromCommit sum: #value) - churnSpecificFromCommit size)).
×
UNCOV
226
                                                                                (#churnFromCommitCreatorOnly
×
UNCOV
227
                                                    ->
×
UNCOV
228
                                                            ((churnOfAuthorOnly sum: #value) - churnOfAuthorOnly size)).
×
UNCOV
229
                                                   (#churnLoC
×
UNCOV
230
                                                    ->
×
UNCOV
231
                                                            ((churnedContribution sum: #value)
×
UNCOV
232
                                                             - churnedContribution size)) 
×
UNCOV
233
                                        } asDictionary ].
×
UNCOV
234

×
UNCOV
235
                          file -> churnData ].
×
UNCOV
236
        churns := churns reject: [ :file2churn | file2churn value isNil ].
×
UNCOV
237

×
UNCOV
238
        ^ {
×
UNCOV
239
                  (#churns -> churns).
×
UNCOV
240
                  (#details -> perFileChanges) } asDictionary
×
UNCOV
241
]
×
242

243
{ #category : #churn }
UNCOV
244
GitAnalyzer >> computeSpecificChurnOf: commit2Changes [
×
UNCOV
245

×
UNCOV
246
        | changesDic |
×
UNCOV
247
        "1 -> (a GLPHEChange -> NumberOfChurnDetected)"
×
UNCOV
248
        changesDic := OrderedDictionary new.
×
UNCOV
249

×
UNCOV
250

×
UNCOV
251
        (commit2Changes sortAscending: [ :assoc | assoc key created_at ])
×
UNCOV
252
                do: [ :entry |
×
UNCOV
253
                        | commit diffRanges |
×
UNCOV
254
                        commit := entry key.
×
UNCOV
255
                        diffRanges := entry value.
×
UNCOV
256

×
UNCOV
257
                        diffRanges do: [ :diff |
×
UNCOV
258
                                | from |
×
UNCOV
259
                                from := (diff originalLineRange
×
UNCOV
260
                                                 copyFrom: (diff originalLineRange indexOf: $-) + 1
×
UNCOV
261
                                                 to: (diff originalLineRange
×
UNCOV
262
                                                                  indexOf: $,
×
UNCOV
263
                                                                  ifAbsent: [ diff originalLineRange size + 1 ]) - 1)
×
UNCOV
264
                                                asString asNumber.
×
UNCOV
265
                                from = 0 ifTrue: [ from := 1 ].
×
UNCOV
266
                                self insertDiff: diff into: changesDic startingFrom: from ] ].
×
UNCOV
267

×
UNCOV
268

×
UNCOV
269

×
UNCOV
270
        ^ self sortChangeDic: changesDic
×
UNCOV
271
]
×
272

273
{ #category : #accessing }
UNCOV
274
GitAnalyzer >> firstAmandmentFromChrun: aChurnAnalysis [ 
×
UNCOV
275
        |details whereChangesOccurs firstCommitsPerFile|
×
UNCOV
276
        
×
UNCOV
277
        whereChangesOccurs := (aChurnAnalysis at: #churns ) select: [ :file | (file value at: #churnLoC) > 0 ].
×
UNCOV
278
        
×
UNCOV
279
        details := whereChangesOccurs collect: [ :file |
×
UNCOV
280
                ((aChurnAnalysis at: #details) detect: [ :entry | entry key = file key] )
×
UNCOV
281
                 ].
×
UNCOV
282
        
×
UNCOV
283
        firstCommitsPerFile := details collect: [ :perFile |
×
UNCOV
284
                |changes firstCommits first|
×
UNCOV
285
                changes := perFile value.
×
UNCOV
286
                changes := changes select: [ :line2changes | line2changes value value > 1  ].
×
UNCOV
287
                firstCommits := (changes collect: [ :line2changes |  line2changes key second diffRange diff commit ]) values. 
×
UNCOV
288
                first := (firstCommits sortAscending: [:c | c created_at ]) first.
×
UNCOV
289
                 ].
×
UNCOV
290
        
×
UNCOV
291

×
UNCOV
292
        ^ (firstCommitsPerFile sortAscending: [:c | c created_at ]) ifEmpty: nil ifNotEmpty: [ :v | v first ]  . 
×
UNCOV
293

×
UNCOV
294
]
×
295

296
{ #category : #accessing }
UNCOV
297
GitAnalyzer >> fromCommit: aCommit [
×
UNCOV
298
        fromCommit := aCommit. 
×
UNCOV
299
]
×
300

301
{ #category : #accessing }
UNCOV
302
GitAnalyzer >> glhImporter: anImporter [
×
UNCOV
303
        glhImporter := anImporter .
×
UNCOV
304
]
×
305

306
{ #category : #'as yet unclassified' }
307
GitAnalyzer >> impactedFilesInFollowUpCommitsOf: aGLHCommit [
×
308

×
309
        ^ self
×
310
                  impactedFilesInFollowUpCommitsOf: aGLHCommit
×
311
                  withMaxCommits: self maxChildCommits. 
×
312
]
×
313

314
{ #category : #churn }
UNCOV
315
GitAnalyzer >> impactedFilesInFollowUpCommitsOf: aGLHCommit withMaxCommits: max [
×
UNCOV
316

×
UNCOV
317
        | commitFiles |
×
UNCOV
318
        commitFiles := (fromCommit diffs collect: [ :diff |
×
UNCOV
319
                                diff new_path -> (Set new
×
UNCOV
320
                                         add: aGLHCommit -> diff diffRanges;
×
UNCOV
321
                                         yourself) ]) asDictionary.
×
UNCOV
322

×
UNCOV
323
        self
×
UNCOV
324
                visitChildCommits: fromCommit childCommits
×
UNCOV
325
                lookingForFiles: commitFiles upto: max.
×
UNCOV
326

×
UNCOV
327
        ^ commitFiles
×
UNCOV
328
]
×
329

330
{ #category : #initialization }
UNCOV
331
GitAnalyzer >> initialize [
×
UNCOV
332

×
UNCOV
333
        glModel := GLPHEModel new.
×
UNCOV
334
        fromCommit := GLHCommit new.
×
UNCOV
335
        glhImporter := GLPHModelImporter new.
×
UNCOV
336
        onProject := GLHProject new.
×
UNCOV
337
        maxChildCommits := -1
×
UNCOV
338
]
×
339

340
{ #category : #insertion }
UNCOV
341
GitAnalyzer >> insertDiff: aGLPHEDiffRange into: fileChangesDic startingFrom: from [ 
×
UNCOV
342
        |index|
×
UNCOV
343
        index := from. 
×
UNCOV
344
        aGLPHEDiffRange changes do: [ :aChange |
×
UNCOV
345
        
×
UNCOV
346
                aChange isAddition ifTrue: [ 
×
UNCOV
347
                        fileChangesDic at: index ifPresent: [ :current | 
×
UNCOV
348
                         
×
UNCOV
349
                        current key add: aChange.
×
UNCOV
350
                        current value: current value + 1.  ] ifAbsentPut: [((OrderedCollection new add: aChange; yourself) -> 1 ) ].
×
UNCOV
351
                         ].
×
UNCOV
352
                
×
UNCOV
353
                aChange isDeletion ifFalse: [ index := index + 1 ]. 
×
UNCOV
354
                
×
UNCOV
355
                 ]
×
UNCOV
356
]
×
357

358
{ #category : #accessing }
UNCOV
359
GitAnalyzer >> maxChildCommit: max [ 
×
UNCOV
360
        maxChildCommits := max
×
UNCOV
361
]
×
362

363
{ #category : #accessing }
UNCOV
364
GitAnalyzer >> maxChildCommits [
×
UNCOV
365
        ^ maxChildCommits
×
UNCOV
366
]
×
367

368
{ #category : #'as yet unclassified' }
UNCOV
369
GitAnalyzer >> onModel: agitHealthModel [
×
UNCOV
370
        glModel := agitHealthModel
×
UNCOV
371
]
×
372

373
{ #category : #accessing }
UNCOV
374
GitAnalyzer >> onProject: aGLHProject [ 
×
UNCOV
375
        onProject := aGLHProject
×
UNCOV
376
]
×
377

378
{ #category : #sorting }
UNCOV
379
GitAnalyzer >> sortChangeDic: aCollection [ 
×
UNCOV
380
        ^ (aCollection associations sortAscending: [ :e | e key ] ) asOrderedDictionary 
×
UNCOV
381
]
×
382

383
{ #category : #visiting }
384
GitAnalyzer >> visitChildCommits: commits lookingForFiles: commitFiles [
×
385

×
386
        ^ self visitChildCommits:  commits lookingForFiles: commitFiles upto: -1 
×
387
]
×
388

389
{ #category : #visiting }
UNCOV
390
GitAnalyzer >> visitChildCommits: commits lookingForFiles: commitFiles upto: nCommits [
×
UNCOV
391

×
UNCOV
392
        commits ifEmpty: [ ^ commitFiles ].
×
UNCOV
393
        (nCommits = 0) ifTrue: [ ^ commitFiles ].
×
UNCOV
394

×
UNCOV
395
        commits do: [ :commit |
×
UNCOV
396
                | files |
×
UNCOV
397
                files := commit diffs collect: [ :diff | diff ].
×
UNCOV
398

×
UNCOV
399
                files do: [ :diff |
×
UNCOV
400
                        commitFiles
×
UNCOV
401
                                at: diff new_path
×
UNCOV
402
                                ifPresent: [ :v | v add: commit -> diff diffRanges ]
×
UNCOV
403
                                ifAbsent: [  ] ].
×
UNCOV
404

×
UNCOV
405
                self
×
UNCOV
406
                        visitChildCommits: commit childCommits
×
UNCOV
407
                        lookingForFiles: commitFiles
×
UNCOV
408
                        upto: nCommits - 1 ].
×
UNCOV
409

×
UNCOV
410
        ^ commitFiles
×
UNCOV
411
]
×
412

413
{ #category : #visiting }
UNCOV
414
GitAnalyzer >> visitChildCommits: commits toStoreThemIn: commitsFound upto: nCommits [
×
UNCOV
415

×
UNCOV
416
        commits ifEmpty: [ ^ commitsFound ].
×
UNCOV
417
        nCommits = 0 ifTrue: [ ^ commitsFound ].
×
UNCOV
418

×
UNCOV
419
        commits do: [ :commit |
×
UNCOV
420
                commitsFound add: commit.
×
UNCOV
421

×
UNCOV
422
                self
×
UNCOV
423
                        visitChildCommits: commit childCommits
×
UNCOV
424
                        toStoreThemIn: commitsFound
×
UNCOV
425
                        upto: nCommits - 1 ].
×
UNCOV
426

×
UNCOV
427
        ^ commitsFound
×
UNCOV
428
]
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc