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

moosetechnology / GitProjectHealth / 10735071564

06 Sep 2024 08:26AM UTC coverage: 18.133%. First build
10735071564

Pull #52

github

web-flow
Merge d353ae61e into 4f01fb9bb
Pull Request #52: test: add tests for user metrics

0 of 4 new or added lines in 4 files covered. (0.0%)

1589 of 8763 relevant lines covered (18.13%)

0.18 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 }
15
GitAnalyzer >> analyseChurn [
×
16

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

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

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

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

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

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

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

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

×
82

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

×
87
        ^ commits
×
88
]
×
89

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

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

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

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

×
111
        ^ commits
×
112
]
×
113

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

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

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

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

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

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

×
155
                          creationDate := aGLHPMergeRequest created_at asDateAndTime.
×
156

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

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

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

×
169
        | date2commits |
×
170
        date2commits := Dictionary new.
×
171

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

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

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

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

×
198

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

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

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

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

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

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

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

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

×
251

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

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

×
269

×
270

×
271
        ^ self sortChangeDic: changesDic
×
272
]
×
273

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

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

×
295
]
×
296

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

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

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

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

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

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

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

×
328
        ^ commitFiles
×
329
]
×
330

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

×
411
        ^ commitFiles
×
412
]
×
413

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

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

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

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

×
428
        ^ commitsFound
×
429
]
×
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