From ee6e471bc6677134d01f69e66513a7b5127f1646 Mon Sep 17 00:00:00 2001 From: Benoit Verhaeghe Date: Wed, 5 Aug 2026 16:07:45 +0200 Subject: [PATCH 01/75] begin local importer --- .../GitLocalModelImporter.class.st | 56 +++++++++++++++++++ .../package.st | 1 + 2 files changed, 57 insertions(+) create mode 100644 src/GitProjectHealth-Model-LocalImporter/GitLocalModelImporter.class.st create mode 100644 src/GitProjectHealth-Model-LocalImporter/package.st diff --git a/src/GitProjectHealth-Model-LocalImporter/GitLocalModelImporter.class.st b/src/GitProjectHealth-Model-LocalImporter/GitLocalModelImporter.class.st new file mode 100644 index 00000000..446df439 --- /dev/null +++ b/src/GitProjectHealth-Model-LocalImporter/GitLocalModelImporter.class.st @@ -0,0 +1,56 @@ +" +Based on the local path of a git repository, this project fill as possible a model +" +Class { + #name : #GitLocalModelImporter, + #superclass : #GitModelImporter, + #instVars : [ + 'localGitRepositoryFileReference', + 'gitRepository' + ], + #category : #'GitProjectHealth-Model-LocalImporter' +} + +{ #category : #'import - commits' } +GitLocalModelImporter >> gitRepository [ + + ^ (LGitRepository on: localGitRepositoryFileReference) + init; + yourself +] + +{ #category : #importCommit } +GitLocalModelImporter >> importCommit: aLGitCommit [ + + self model add: (GLHCommit new + id: aLGitCommit name; + message: aLGitCommit message; + committed_date: aLGitCommit time asDateAndTime; + committer_email: aLGitCommit author email; + committer_name: aLGitCommit author name yourself) +] + +{ #category : #'import - commits' } +GitLocalModelImporter >> importLatestCommitsOfProject [ + + | revwalk i | + i := 0. + revwalk := (LGitRevwalk of: self gitRepository). + revwalk pushHead. + [revwalk hasNext and: [i < 50]] whileTrue: [ + self importCommit: revwalk next + ] +] + +{ #category : #'import - commits' } +GitLocalModelImporter >> importLatestCommitsOfProject: aGLHProject [ + "this importer load based on inner state local git repository" + + self importLatestCommitsOfProject +] + +{ #category : #accessing } +GitLocalModelImporter >> localGitRepository: aFileReference [ + + localGitRepositoryFileReference := aFileReference +] diff --git a/src/GitProjectHealth-Model-LocalImporter/package.st b/src/GitProjectHealth-Model-LocalImporter/package.st new file mode 100644 index 00000000..7cb970b8 --- /dev/null +++ b/src/GitProjectHealth-Model-LocalImporter/package.st @@ -0,0 +1 @@ +Package { #name : #'GitProjectHealth-Model-LocalImporter' } From 78bce6e5142d9c2f1290bf9fe5f233fa3f53a4e1 Mon Sep 17 00:00:00 2001 From: Benoit Verhaeghe Date: Wed, 5 Aug 2026 16:40:07 +0200 Subject: [PATCH 02/75] chain the commit --- .../GitLocalModelImporter.class.st | 48 ++++++++++++++----- 1 file changed, 37 insertions(+), 11 deletions(-) diff --git a/src/GitProjectHealth-Model-LocalImporter/GitLocalModelImporter.class.st b/src/GitProjectHealth-Model-LocalImporter/GitLocalModelImporter.class.st index 446df439..0ef36857 100644 --- a/src/GitProjectHealth-Model-LocalImporter/GitLocalModelImporter.class.st +++ b/src/GitProjectHealth-Model-LocalImporter/GitLocalModelImporter.class.st @@ -22,24 +22,43 @@ GitLocalModelImporter >> gitRepository [ { #category : #importCommit } GitLocalModelImporter >> importCommit: aLGitCommit [ - self model add: (GLHCommit new - id: aLGitCommit name; - message: aLGitCommit message; - committed_date: aLGitCommit time asDateAndTime; - committer_email: aLGitCommit author email; - committer_name: aLGitCommit author name yourself) + ^ self glhModel add: (GLHCommit new + id: aLGitCommit name; + message: aLGitCommit message; + committed_date: aLGitCommit time asDateAndTime; + committer_email: aLGitCommit author email; + committer_name: aLGitCommit author name; + parent_ids: + (aLGitCommit parents collect: [ :parent | parent name ]); + yourself) +] + +{ #category : #'import - commits' } +GitLocalModelImporter >> importDiffOfCommit: commit [ + self halt. ] { #category : #'import - commits' } GitLocalModelImporter >> importLatestCommitsOfProject [ - | revwalk i | + | revwalk i commits | i := 0. - revwalk := (LGitRevwalk of: self gitRepository). + revwalk := LGitRevwalk of: self gitRepository. + commits := OrderedCollection new. revwalk pushHead. - [revwalk hasNext and: [i < 50]] whileTrue: [ - self importCommit: revwalk next - ] + [ revwalk hasNext and: [ i < 50 ] ] whileTrue: [ + | gitCommit | + i := i + 1. + gitCommit := revwalk next. + commits add: (self importCommit: gitCommit) ]. + + self chainsCommitsFrom: commits + + "self withCommitDiffs ifTrue: [ + commits do: [ :commit | + revwalk pushCommit: commit id. + self halt. + self importDiffOfCommit: commit ] ]" ] { #category : #'import - commits' } @@ -49,6 +68,13 @@ GitLocalModelImporter >> importLatestCommitsOfProject: aGLHProject [ self importLatestCommitsOfProject ] +{ #category : #'import - commits' } +GitLocalModelImporter >> initialize [ + + super initialize. + withCommitDiffs := true +] + { #category : #accessing } GitLocalModelImporter >> localGitRepository: aFileReference [ From 240cf327540958c796e1ab8daf62dd7c440bb16b Mon Sep 17 00:00:00 2001 From: Benoit Verhaeghe Date: Thu, 6 Aug 2026 11:25:16 +0200 Subject: [PATCH 03/75] can import git patch --- .../GitLocalModelImporter.class.st | 71 +++++++++++++------ .../LGitPatch.extension.st | 21 ++++++ 2 files changed, 72 insertions(+), 20 deletions(-) create mode 100644 src/GitProjectHealth-Model-LocalImporter/LGitPatch.extension.st diff --git a/src/GitProjectHealth-Model-LocalImporter/GitLocalModelImporter.class.st b/src/GitProjectHealth-Model-LocalImporter/GitLocalModelImporter.class.st index 0ef36857..c3b9929c 100644 --- a/src/GitProjectHealth-Model-LocalImporter/GitLocalModelImporter.class.st +++ b/src/GitProjectHealth-Model-LocalImporter/GitLocalModelImporter.class.st @@ -11,6 +11,22 @@ Class { #category : #'GitProjectHealth-Model-LocalImporter' } +{ #category : #'private - diffs' } +GitLocalModelImporter >> createDiffFrom: patch and: delta [ + + | glhDiff | + glhDiff := self glhModel newDiff. + glhDiff new_path: delta newFile path. + glhDiff old_path: delta oldFile path. + delta status = LGitDeltaTypeEnum git_delta_renamed ifTrue: [ + glhDiff renamed_file: true ]. + delta status = LGitDeltaTypeEnum git_delta_added ifTrue: [ + glhDiff new_file: true ]. + delta status = LGitDeltaTypeEnum git_delta_deleted ifTrue: [ + glhDiff deleted_file: true ]. + ^ glhDiff diffString: patch patchString +] + { #category : #'import - commits' } GitLocalModelImporter >> gitRepository [ @@ -19,23 +35,39 @@ GitLocalModelImporter >> gitRepository [ yourself ] -{ #category : #importCommit } +{ #category : #'import - commits' } GitLocalModelImporter >> importCommit: aLGitCommit [ - ^ self glhModel add: (GLHCommit new - id: aLGitCommit name; - message: aLGitCommit message; - committed_date: aLGitCommit time asDateAndTime; - committer_email: aLGitCommit author email; - committer_name: aLGitCommit author name; - parent_ids: - (aLGitCommit parents collect: [ :parent | parent name ]); - yourself) + ^ self glhModel newCommit + id: aLGitCommit name; + message: aLGitCommit message; + committed_date: aLGitCommit time asDateAndTime; + committer_email: aLGitCommit author email; + committer_name: aLGitCommit author name; + parent_ids: + (aLGitCommit parents collect: [ :parent | parent name ]); + yourself ] -{ #category : #'import - commits' } +{ #category : #'import - diffs' } GitLocalModelImporter >> importDiffOfCommit: commit [ - self halt. + + | sourceCommit targetCommit gitDiff | + commit parentCommits ifNotEmpty: [ + sourceCommit := commit + cacheAt: #importerOrigin + ifAbsent: [ + Error signal: 'missing importer origin' ]. + targetCommit := commit parentCommits anyOne + cacheAt: #importerOrigin + ifAbsent: [ + Error signal: 'missing importer origin' ]. + gitDiff := sourceCommit tree diffTo: targetCommit tree. + (1 to: gitDiff numberOfDeltas) collect: [ :idx | + | delta patch | + delta := gitDiff deltas at: idx. + patch := gitDiff patchAt: idx. + self createDiffFrom: patch and: delta ] ] ] { #category : #'import - commits' } @@ -47,18 +79,17 @@ GitLocalModelImporter >> importLatestCommitsOfProject [ commits := OrderedCollection new. revwalk pushHead. [ revwalk hasNext and: [ i < 50 ] ] whileTrue: [ - | gitCommit | + | gitCommit glhCommit | i := i + 1. gitCommit := revwalk next. - commits add: (self importCommit: gitCommit) ]. + glhCommit := self importCommit: gitCommit. + commits add: glhCommit. + glhCommit cacheAt: #importerOrigin put: gitCommit ]. - self chainsCommitsFrom: commits + self chainsCommitsFrom: commits. - "self withCommitDiffs ifTrue: [ - commits do: [ :commit | - revwalk pushCommit: commit id. - self halt. - self importDiffOfCommit: commit ] ]" + self withCommitDiffs ifTrue: [ + commits do: [ :commit | self importDiffOfCommit: commit ] ] ] { #category : #'import - commits' } diff --git a/src/GitProjectHealth-Model-LocalImporter/LGitPatch.extension.st b/src/GitProjectHealth-Model-LocalImporter/LGitPatch.extension.st new file mode 100644 index 00000000..f7750111 --- /dev/null +++ b/src/GitProjectHealth-Model-LocalImporter/LGitPatch.extension.st @@ -0,0 +1,21 @@ +Extension { #name : #LGitPatch } + +{ #category : #'*GitProjectHealth-Model-LocalImporter' } +LGitPatch >> patchString [ + | buf patchString | + buf := LGitBuf new. + self patch_to_buf: buf. + patchString := buf prim_ptr utf8StringFromCString. + buf free. + ^ patchString +] + +{ #category : #'*GitProjectHealth-Model-LocalImporter' } +LGitPatch >> patch_to_buf: out [ + + ^ self + ffiCallSafely: + #( LGitReturnCodeEnum git_patch_to_buf #( LGitBuf * out , LGitPatch + self ) ) + options: #( optMayGC optCoerceNilToNull ) +] From b8fa838c3972554ac48b64bc3b684ebd4119e12c Mon Sep 17 00:00:00 2001 From: Benoit Verhaeghe Date: Thu, 6 Aug 2026 13:57:02 +0200 Subject: [PATCH 04/75] organize tests parse diff --- .../GitlabModelImporterRemoteTest.class.st | 2 +- .../GPModelImporterTest.class.st | 25 ------- .../GitModelImporterTest.class.st | 72 +++++++++++++++++++ .../package.st | 2 +- .../GitModelImporter.class.st | 2 +- .../GitLocalModelImporter.class.st | 19 +++-- 6 files changed, 87 insertions(+), 35 deletions(-) delete mode 100644 src/GitProjectHealth-Model-Importer-Tests/GPModelImporterTest.class.st create mode 100644 src/GitProjectHealth-Model-Importer-Tests/GitModelImporterTest.class.st diff --git a/src/GitLabHealth-Model-Importer-Tests/GitlabModelImporterRemoteTest.class.st b/src/GitLabHealth-Model-Importer-Tests/GitlabModelImporterRemoteTest.class.st index 1764e285..69d48f83 100644 --- a/src/GitLabHealth-Model-Importer-Tests/GitlabModelImporterRemoteTest.class.st +++ b/src/GitLabHealth-Model-Importer-Tests/GitlabModelImporterRemoteTest.class.st @@ -31,7 +31,7 @@ GitlabModelImporterRemoteTest >> model [ ^ self class resources first current model ] -{ #category : #test } +{ #category : #tests } GitlabModelImporterRemoteTest >> testFilterCommitChanges [ | importer collection resultCollection | diff --git a/src/GitProjectHealth-Model-Importer-Tests/GPModelImporterTest.class.st b/src/GitProjectHealth-Model-Importer-Tests/GPModelImporterTest.class.st deleted file mode 100644 index 12909087..00000000 --- a/src/GitProjectHealth-Model-Importer-Tests/GPModelImporterTest.class.st +++ /dev/null @@ -1,25 +0,0 @@ -" -A GPModelImporterTest is a test class for testing the behavior of GPModelImporter -" -Class { - #name : #GPModelImporterTest, - #superclass : #TestCase, - #category : #'GitProjectHealth-Model-Importer-Tests' -} - -{ #category : #tests } -GPModelImporterTest >> testParseDiffString [ - - "Given" - | diff diffRange | - diff := GLHDiff new. - - diff diffString: '--- a/etc/name.properties -+++ b/etc/new-name.properties'. - - "When" - diffRange := GitModelImporter new parseDiffString: diff. - - "Then" - self assert: diffRange equals: diff diffRanges. -] diff --git a/src/GitProjectHealth-Model-Importer-Tests/GitModelImporterTest.class.st b/src/GitProjectHealth-Model-Importer-Tests/GitModelImporterTest.class.st new file mode 100644 index 00000000..8efd5ddc --- /dev/null +++ b/src/GitProjectHealth-Model-Importer-Tests/GitModelImporterTest.class.st @@ -0,0 +1,72 @@ +" +A GitModelImporterTest is a test class for testing the behavior of GitModelImporter +" +Class { + #name : #GitModelImporterTest, + #superclass : #TestCase, + #instVars : [ + 'model', + 'importer' + ], + #category : #'GitProjectHealth-Model-Importer-Tests' +} + +{ #category : #tests } +GitModelImporterTest >> setUp [ + + super setUp. + model := GLHModel new. + importer := GitModelImporter new. + importer glhModel: model. + importer withCommitDiffs: true +] + +{ #category : #tests } +GitModelImporterTest >> testParseDiffString [ + + "Given" + | diff diffRange | + diff := GLHDiff new. + + diff diffString: '--- a/etc/name.properties ++++ b/etc/new-name.properties'. + + "When" + diffRange := importer parseDiffString: diff. + + "Then" + self assert: diffRange equals: diff diffRanges. +] + +{ #category : #tests } +GitModelImporterTest >> testParseDiffStringComplete [ + + | diff diffRange | + diff := GLHDiff new. + diff new_path: 'new/test/path.txt'. + model add: diff. + diff diffString: + '@@ -15,9 +15,9 @@ LLMModifierTest >> testGetViolationsMessage [ + message1 := ''message1''. + message2 := ''message2''. + +- violations := OrderedCollection new. ++ violations := MooseSpecializedGroup new. + violations add: (Violation new message: message1). +- ++ + violations add: (Violation new message: message2). + violations add: (Violation new message: message2).'. + importer parseDiffString: diff. + self assert: diff diffRanges size equals: 1. + diffRange := diff diffRanges anyOne. + self assert: diffRange changes size equals: 9. + self + assert: + (diffRange changes asMooseGroup allWithType: GLHDeletion) size + equals: 2. + self + assert: + (diffRange changes asMooseGroup allWithType: GLHAddition) size + equals: 2 +] diff --git a/src/GitProjectHealth-Model-Importer-Tests/package.st b/src/GitProjectHealth-Model-Importer-Tests/package.st index 07161f7e..65c2ab3e 100644 --- a/src/GitProjectHealth-Model-Importer-Tests/package.st +++ b/src/GitProjectHealth-Model-Importer-Tests/package.st @@ -1 +1 @@ -Package { #name : #'GitProjectHealth-Model-Importer-Tests' } +Package { #name : #'GitProjectHealth-Model-Importer-Tests' } diff --git a/src/GitProjectHealth-Model-Importer/GitModelImporter.class.st b/src/GitProjectHealth-Model-Importer/GitModelImporter.class.st index e048398c..d9efb699 100644 --- a/src/GitProjectHealth-Model-Importer/GitModelImporter.class.st +++ b/src/GitProjectHealth-Model-Importer/GitModelImporter.class.st @@ -745,7 +745,7 @@ GitModelImporter >> parseDiffString: aDiff [ lines do: [ :line | | aChange | firstChangeRange := false. - (line trim beginsWith: #'@@') ifTrue: [ + (line trimLeft beginsWith: #'@@') ifTrue: [ range := GLHDiffRange newFrom: line. firstChangeRange := true. range := aDiff diffRanges add: range unless: [ :existing :new | diff --git a/src/GitProjectHealth-Model-LocalImporter/GitLocalModelImporter.class.st b/src/GitProjectHealth-Model-LocalImporter/GitLocalModelImporter.class.st index c3b9929c..9d928a95 100644 --- a/src/GitProjectHealth-Model-LocalImporter/GitLocalModelImporter.class.st +++ b/src/GitProjectHealth-Model-LocalImporter/GitLocalModelImporter.class.st @@ -24,7 +24,8 @@ GitLocalModelImporter >> createDiffFrom: patch and: delta [ glhDiff new_file: true ]. delta status = LGitDeltaTypeEnum git_delta_deleted ifTrue: [ glhDiff deleted_file: true ]. - ^ glhDiff diffString: patch patchString + glhDiff diffString: (patch patchString allButFirstLines: 2). + ^ glhDiff ] { #category : #'import - commits' } @@ -63,11 +64,15 @@ GitLocalModelImporter >> importDiffOfCommit: commit [ ifAbsent: [ Error signal: 'missing importer origin' ]. gitDiff := sourceCommit tree diffTo: targetCommit tree. - (1 to: gitDiff numberOfDeltas) collect: [ :idx | - | delta patch | - delta := gitDiff deltas at: idx. - patch := gitDiff patchAt: idx. - self createDiffFrom: patch and: delta ] ] + commit diffs: ((1 to: gitDiff numberOfDeltas) collect: [ :idx | + | delta patch | + delta := gitDiff deltas at: idx. + patch := gitDiff patchAt: idx. + self createDiffFrom: patch and: delta ]) ]. + + "changes are added into the model during the import" + commit diffs do: [ :diff | self importDiffRangesForDiff: diff ]. + ^ commit diffs ] { #category : #'import - commits' } @@ -99,7 +104,7 @@ GitLocalModelImporter >> importLatestCommitsOfProject: aGLHProject [ self importLatestCommitsOfProject ] -{ #category : #'import - commits' } +{ #category : #initialization } GitLocalModelImporter >> initialize [ super initialize. From f96edfed386e6627eff6db51057ec537fc7e7a1a Mon Sep 17 00:00:00 2001 From: Benoit Verhaeghe Date: Thu, 6 Aug 2026 13:57:15 +0200 Subject: [PATCH 05/75] add String allButFirstLines utility --- .../String.extension.st | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 src/GitProjectHealth-Model-LocalImporter/String.extension.st diff --git a/src/GitProjectHealth-Model-LocalImporter/String.extension.st b/src/GitProjectHealth-Model-LocalImporter/String.extension.st new file mode 100644 index 00000000..eca88c1d --- /dev/null +++ b/src/GitProjectHealth-Model-LocalImporter/String.extension.st @@ -0,0 +1,15 @@ +Extension { #name : #String } + +{ #category : #'*GitProjectHealth-Model-LocalImporter' } +String >> allButFirstLines: aNumberOfLine [ + + | stream skipped | + stream := ReadStream on: self. + skipped := 0. + + [ stream atEnd or: [ skipped >= aNumberOfLine ] ] whileFalse: [ + stream upTo: Character lf. + skipped := skipped + 1 ]. + + ^ stream upToEnd +] From 521150ed49259a5000827161d74db892dedd7c0b Mon Sep 17 00:00:00 2001 From: Benoit Verhaeghe Date: Thu, 6 Aug 2026 14:08:59 +0200 Subject: [PATCH 06/75] add test --- .../GitModelImporterTest.class.st | 29 +++++++++++++++++++ .../GitModelImporter.class.st | 10 +++---- 2 files changed, 34 insertions(+), 5 deletions(-) diff --git a/src/GitProjectHealth-Model-Importer-Tests/GitModelImporterTest.class.st b/src/GitProjectHealth-Model-Importer-Tests/GitModelImporterTest.class.st index 8efd5ddc..9d5426ef 100644 --- a/src/GitProjectHealth-Model-Importer-Tests/GitModelImporterTest.class.st +++ b/src/GitProjectHealth-Model-Importer-Tests/GitModelImporterTest.class.st @@ -21,6 +21,19 @@ GitModelImporterTest >> setUp [ importer withCommitDiffs: true ] +{ #category : #tests } +GitModelImporterTest >> testFilterCommitChanges [ + + | string | + string := '--- hello + world + +++ how + \ No newline at end of file + you' lines. + self assert: (importer filterCommitChanges: string) equals: ' world + you' lines +] + { #category : #tests } GitModelImporterTest >> testParseDiffString [ @@ -70,3 +83,19 @@ GitModelImporterTest >> testParseDiffStringComplete [ (diffRange changes asMooseGroup allWithType: GLHAddition) size equals: 2 ] + +{ #category : #tests } +GitModelImporterTest >> testParseEmptyDiffString [ + "Given" + + | diff diffRange | + diff := GLHDiff new. + + diff diffString: ''. + + "When" + diffRange := importer parseDiffString: diff. + + "Then" + self assert: diffRange isEmpty +] diff --git a/src/GitProjectHealth-Model-Importer/GitModelImporter.class.st b/src/GitProjectHealth-Model-Importer/GitModelImporter.class.st index d9efb699..648b3fce 100644 --- a/src/GitProjectHealth-Model-Importer/GitModelImporter.class.st +++ b/src/GitProjectHealth-Model-Importer/GitModelImporter.class.st @@ -732,18 +732,18 @@ GitModelImporter >> makeGlobal: aLabel [ GitModelImporter >> parseDiffString: aDiff [ "parse diff string to create entities for each lines" - | lines range index firstChangeRange | + | lines index firstChangeRange | + + aDiff diffString ifEmpty: [ ^ aDiff diffRanges ]. + lines := aDiff diffString lines. index := 0. - range := nil. - - lines ifEmpty: [ ^ aDiff diffRanges ]. lines := self filterCommitChanges: lines. lines ifEmpty: [ ^ aDiff diffRanges ]. (lines first beginsWith: '@@') ifFalse: [ ^ aDiff diffRanges ]. lines do: [ :line | - | aChange | + | aChange range | firstChangeRange := false. (line trimLeft beginsWith: #'@@') ifTrue: [ range := GLHDiffRange newFrom: line. From 6adc18e0b50b830d37cf478ebda209831cd3da6d Mon Sep 17 00:00:00 2001 From: Benoit Verhaeghe Date: Thu, 6 Aug 2026 14:19:18 +0200 Subject: [PATCH 07/75] refix tests --- src/GitLabHealth-Model-Extension/GLHChange.extension.st | 2 +- src/GitProjectHealth-Model-Importer/GitModelImporter.class.st | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/GitLabHealth-Model-Extension/GLHChange.extension.st b/src/GitLabHealth-Model-Extension/GLHChange.extension.st index d72c888f..26bfe17f 100644 --- a/src/GitLabHealth-Model-Extension/GLHChange.extension.st +++ b/src/GitLabHealth-Model-Extension/GLHChange.extension.st @@ -31,7 +31,7 @@ GLHChange class >> newFrom: aDiffLine [ aChange := (trimedLine beginsWith: #+) ifTrue: [ GLHAddition new ] ifFalse: [ - (aDiffLine trim beginsWith: #-) + (trimedLine beginsWith: #-) ifTrue: [ GLHDeletion new ] ifFalse: [ GLHLineOfCode new ] ]. diff --git a/src/GitProjectHealth-Model-Importer/GitModelImporter.class.st b/src/GitProjectHealth-Model-Importer/GitModelImporter.class.st index 648b3fce..94ee8aea 100644 --- a/src/GitProjectHealth-Model-Importer/GitModelImporter.class.st +++ b/src/GitProjectHealth-Model-Importer/GitModelImporter.class.st @@ -732,7 +732,7 @@ GitModelImporter >> makeGlobal: aLabel [ GitModelImporter >> parseDiffString: aDiff [ "parse diff string to create entities for each lines" - | lines index firstChangeRange | + | lines index firstChangeRange range | aDiff diffString ifEmpty: [ ^ aDiff diffRanges ]. @@ -743,7 +743,7 @@ GitModelImporter >> parseDiffString: aDiff [ (lines first beginsWith: '@@') ifFalse: [ ^ aDiff diffRanges ]. lines do: [ :line | - | aChange range | + | aChange | firstChangeRange := false. (line trimLeft beginsWith: #'@@') ifTrue: [ range := GLHDiffRange newFrom: line. From c1b7b0543882b70554cd847c6882d044ba1420e5 Mon Sep 17 00:00:00 2001 From: Benoit Verhaeghe Date: Thu, 6 Aug 2026 16:55:28 +0200 Subject: [PATCH 08/75] introducing addAll:keyedBy: that check key to not readd element. More efficient than the comparison version when identity is easy to check (using only = for instance) --- .../GLHExtensionTest.class.st | 181 ++++++++++++++++++ .../MooseAbstractGroup.extension.st | 45 +++++ .../OrderedCollection.extension.st | 28 +++ .../GitModelImporter.class.st | 23 ++- 4 files changed, 267 insertions(+), 10 deletions(-) diff --git a/src/GitLabHealth-Model-Extension-Tests/GLHExtensionTest.class.st b/src/GitLabHealth-Model-Extension-Tests/GLHExtensionTest.class.st index 09169747..50e0bb9c 100644 --- a/src/GitLabHealth-Model-Extension-Tests/GLHExtensionTest.class.st +++ b/src/GitLabHealth-Model-Extension-Tests/GLHExtensionTest.class.st @@ -14,6 +14,177 @@ GLHExtensionTest >> setUp [ model := GLHModel new ] +{ #category : #tests } +GLHExtensionTest >> testAddAllKeyedBy [ + + | col | + col := OrderedCollection new. + col addAll: { 12 } keyedBy: [ :a | a ]. + self assert: col size equals: 1. + self assert: col first equals: 12 +] + +{ #category : #tests } +GLHExtensionTest >> testAddAllKeyedByIntersectionOfUsers [ + + | col result user1 user2 user3 user2b user3b user4 | + user1 := GLHUser new. + user1 id: 1. + user2 := GLHUser new. + user2 id: 2. + user3 := GLHUser new. + user3 id: 3. + col := OrderedCollection new. + col + addAll: { + user1. + user2. + user3 } + keyedBy: [ :a | a id ]. + self assert: col size equals: 3. + user2b := GLHUser new. + user2b id: 2. + user3b := GLHUser new. + user3b id: 3. + user4 := GLHUser new. + user4 id: 4. + + result := col + addAll: { + user2b. + user3b. + user4 } + keyedBy: [ :a | a id ]. + self assert: col size equals: 4. + self assert: result size equals: 3. + self assert: (result includes: user2). + self assert: (result includes: user3). + self assert: (result includes: user4) +] + +{ #category : #tests } +GLHExtensionTest >> testAddAllKeyedByTwiceEqualityIsTheSameButNotObject [ + + | col user result userB | + user := GLHUser new. + user id: 12. + col := OrderedCollection new. + col addAll: { user } keyedBy: [ :a | a id ]. + self assert: col size equals: 1. + self assert: col first identicalTo: user. + userB := GLHUser new. + userB id: 12. + result := col addAll: { userB } keyedBy: [ :a | a id ]. + self assert: col size equals: 1. + self assert: result first identicalTo: user +] + +{ #category : #tests } +GLHExtensionTest >> testAddAllKeyedByTwiceTheSame [ + + | col user result | + user := GLHUser new. + user id: 12. + col := OrderedCollection new. + col addAll: { user } keyedBy: [ :a | a ]. + self assert: col size equals: 1. + self assert: col first identicalTo: user. + result := col addAll: { user } keyedBy: [ :a | a ]. + self assert: col size equals: 1. + self assert: result first identicalTo: user +] + +{ #category : #tests } +GLHExtensionTest >> testAddAllOfTypeKeyed [ + + | user values | + user := GLHUser new. + user id: 12. + values := model + addAll: { user } + ofType: GLHUser + keyedBy: [ :entry | entry id ]. + self assert: values size equals: 1. + self assert: values anyOne equals: user +] + +{ #category : #tests } +GLHExtensionTest >> testAddAllOfTypeKeyedByDifferentEntity [ + + | user values alreadyExistingUser | + alreadyExistingUser := model newUser. + alreadyExistingUser id: 15. + user := GLHUser new. + user id: 12. + values := model + addAll: { user } + ofType: GLHUser + keyedBy: [ :val | val id ]. + self assert: values size equals: 1. + self assert: model size equals: 2. + self assert: (values includes: user). + self assert: (model includes: user). + self assert: (model includes: alreadyExistingUser) +] + +{ #category : #tests } +GLHExtensionTest >> testAddAllOfTypeKeyedDifferentEntity [ + + | user values alreadyExistingUser | + alreadyExistingUser := model newUser. + alreadyExistingUser id: 15. + user := GLHUser new. + user id: 12. + values := model + addAll: { user } + ofType: GLHUser + keyedBy: [ :a | a id ]. + self assert: values size equals: 1. + self assert: model size equals: 2. + self assert: (values includes: user). + self assert: (model includes: user). + self assert: (model includes: alreadyExistingUser) +] + +{ #category : #tests } +GLHExtensionTest >> testAddAllOfTypeKeyedIntersection [ + + | user1 user2 user3 user2b user3b user4 result | + user1 := GLHUser new. + user1 id: 1. + user2 := GLHUser new. + user2 id: 2. + user3 := GLHUser new. + user3 id: 3. + model + addAll: { + user1. + user2. + user3 } + ofType: GLHUser + keyedBy: [ :entry | entry id ]. + self assert: model size equals: 3. + user2b := GLHUser new. + user2b id: 2. + user3b := GLHUser new. + user3b id: 3. + user4 := GLHUser new. + user4 id: 4. + + result := model + addAll: { + user2b. + user3b. + user4 } + ofType: GLHUser + keyedBy: [ :entry | entry id ]. + self assert: model size equals: 4. + self assert: result size equals: 3. + self assert: (result includes: user2). + self assert: (result includes: user3). + self assert: (result includes: user4) +] + { #category : #tests } GLHExtensionTest >> testAddAllOfTypeUnless [ @@ -189,3 +360,13 @@ GLHExtensionTest >> testAddAllUnlessTwiceTheSame [ self assert: col size equals: 1. self assert: result first identicalTo: user ] + +{ #category : #tests } +GLHExtensionTest >> testAddAllkeyedBy [ + + | col | + col := OrderedCollection new. + col addAll: { 12 } keyedBy: [ :a | a ]. + self assert: col size equals: 1. + self assert: col first equals: 12 +] diff --git a/src/GitLabHealth-Model-Extension/MooseAbstractGroup.extension.st b/src/GitLabHealth-Model-Extension/MooseAbstractGroup.extension.st index 201be1db..69f5e105 100644 --- a/src/GitLabHealth-Model-Extension/MooseAbstractGroup.extension.st +++ b/src/GitLabHealth-Model-Extension/MooseAbstractGroup.extension.st @@ -7,10 +7,55 @@ MooseAbstractGroup >> add: anElmt unless: aConditionAsBlock [ ^ (self addAll: { anElmt } unless: aConditionAsBlock) first ] +{ #category : #'*GitLabHealth-Model-Extension' } +MooseAbstractGroup >> addAll: anElmtCollection keyedBy: aConditionAsBlock [ + "()>>>" + + anElmtCollection ifEmpty: [ ^ {} ]. + ^ self + addAll: anElmtCollection + ofType: anElmtCollection first class + keyedBy: aConditionAsBlock +] + +{ #category : #'*GitLabHealth-Model-Extension' } +MooseAbstractGroup >> addAll: anElmtCollection ofType: aType keyedBy: aKeyBlock [ + + | existingElements elementByKey result | + + existingElements := self allWithType: aType. + + elementByKey := Dictionary new: + existingElements size + anElmtCollection size. + + result := OrderedCollection new: anElmtCollection size. + + "Indexe les éléments déjà présents. + En cas de doublon existant, conserve le premier." + existingElements do: [ :existing | + elementByKey + at: (aKeyBlock value: existing) + ifAbsentPut: [ existing ] ]. + + anElmtCollection do: [ :candidate | + | canonicalElement | + + canonicalElement := elementByKey + at: (aKeyBlock value: candidate) + ifAbsentPut: [ + self add: candidate. + candidate ]. + + result add: canonicalElement ]. + + ^ result +] + { #category : #'*GitLabHealth-Model-Extension' } MooseAbstractGroup >> addAll: anElmtCollection ofType: aType unless: aConditionAsBlock [ | originalCollection returnCollection | + "you should use addAll: anElmtCollection ofType: aType keyedBy: aKeyBlock if possible" originalCollection := (self allWithType: aType). "Create same kind of collection" returnCollection := OrderedCollection new. diff --git a/src/GitLabHealth-Model-Extension/OrderedCollection.extension.st b/src/GitLabHealth-Model-Extension/OrderedCollection.extension.st index db53bb8b..2b196622 100644 --- a/src/GitLabHealth-Model-Extension/OrderedCollection.extension.st +++ b/src/GitLabHealth-Model-Extension/OrderedCollection.extension.st @@ -7,6 +7,34 @@ OrderedCollection >> add: anElmt unless: aConditionAsBlock [ ^ (self addAll: { anElmt } unless: aConditionAsBlock) first ] +{ #category : #'*GitLabHealth-Model-Extension' } +OrderedCollection >> addAll: anElmtCollection keyedBy: aKeyBlock [ + + | elementByKey result | + elementByKey := Dictionary new: self size + anElmtCollection size. + + result := self class new: anElmtCollection size. + + "Indexe les éléments déjà présents. + En cas de doublon existant, conserve le premier." + self do: [ :existing | + elementByKey + at: (aKeyBlock value: existing) + ifAbsentPut: [ existing ] ]. + + anElmtCollection do: [ :candidate | + | canonicalElement | + canonicalElement := elementByKey + at: (aKeyBlock value: candidate) + ifAbsentPut: [ + self add: candidate. + candidate ]. + + result add: canonicalElement ]. + + ^ result +] + { #category : #'*GitLabHealth-Model-Extension' } OrderedCollection >> addAll: anElmtCollection unless: aConditionAsBlock [ diff --git a/src/GitProjectHealth-Model-Importer/GitModelImporter.class.st b/src/GitProjectHealth-Model-Importer/GitModelImporter.class.st index 94ee8aea..ab041077 100644 --- a/src/GitProjectHealth-Model-Importer/GitModelImporter.class.st +++ b/src/GitProjectHealth-Model-Importer/GitModelImporter.class.st @@ -14,12 +14,13 @@ Class { 'userCatalogue', 'repoApi', 'withCommitDiffs', - 'generalReader' + 'generalReader', + 'withCommitDiffsChanges' ], #classVars : [ 'currentImporter' ], - #category : #'GitProjectHealth-Model-Importer' + #category : #'_UnpackagedPackage' } { #category : #bitbucket } @@ -733,17 +734,15 @@ GitModelImporter >> parseDiffString: aDiff [ "parse diff string to create entities for each lines" | lines index firstChangeRange range | - aDiff diffString ifEmpty: [ ^ aDiff diffRanges ]. - + lines := aDiff diffString lines. index := 0. lines := self filterCommitChanges: lines. lines ifEmpty: [ ^ aDiff diffRanges ]. (lines first beginsWith: '@@') ifFalse: [ ^ aDiff diffRanges ]. - lines do: [ :line | - | aChange | + | aChange | firstChangeRange := false. (line trimLeft beginsWith: #'@@') ifTrue: [ range := GLHDiffRange newFrom: line. @@ -763,10 +762,14 @@ GitModelImporter >> parseDiffString: aDiff [ "add changes to the diff range" aDiff diffRanges do: [ :diffRange | - self glhModel addAll: diffRange changes unless: [ :existing :new | - existing diffRange mooseID = new diffRange mooseID and: [ - existing sourceCode = new sourceCode and: [ - existing index = new index ] ] ] ]. + self glhModel + addAll: diffRange changes + ofType: GLHChange + keyedBy: [ :val | + { + val diffRange mooseID. + val sourceCode. + val index } ] ]. ^ aDiff diffRanges ] From 32b5d2a8e490af4208ff72821431efd64d16885c Mon Sep 17 00:00:00 2001 From: Benoit Verhaeghe Date: Thu, 6 Aug 2026 17:06:02 +0200 Subject: [PATCH 09/75] Update GitModelImporter.class.st --- src/GitProjectHealth-Model-Importer/GitModelImporter.class.st | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/GitProjectHealth-Model-Importer/GitModelImporter.class.st b/src/GitProjectHealth-Model-Importer/GitModelImporter.class.st index ab041077..0d2cc2a6 100644 --- a/src/GitProjectHealth-Model-Importer/GitModelImporter.class.st +++ b/src/GitProjectHealth-Model-Importer/GitModelImporter.class.st @@ -20,7 +20,7 @@ Class { #classVars : [ 'currentImporter' ], - #category : #'_UnpackagedPackage' + #category : #'GitProjectHealth-Model-Importer' } { #category : #bitbucket } From dfec645b14358be59f48953b1e4d97b244993436 Mon Sep 17 00:00:00 2001 From: Benoit Verhaeghe Date: Thu, 6 Aug 2026 17:10:52 +0200 Subject: [PATCH 10/75] add local importer in baseline --- .../BaselineOfGitLabHealth.class.st | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/BaselineOfGitLabHealth/BaselineOfGitLabHealth.class.st b/src/BaselineOfGitLabHealth/BaselineOfGitLabHealth.class.st index 2cb40145..c3dc100e 100644 --- a/src/BaselineOfGitLabHealth/BaselineOfGitLabHealth.class.st +++ b/src/BaselineOfGitLabHealth/BaselineOfGitLabHealth.class.st @@ -102,7 +102,8 @@ BaselineOfGitLabHealth >> defineGroups: spec [ 'GitLabHealth-Visualization' 'GitLabProjectHealth-Model-Importer' 'GitProjectHealth-Model-Importer-Tests' 'BitBucketHealth-Model-Importer' - 'BitBucketHealth-Model-Importer-Tests' ). + 'BitBucketHealth-Model-Importer-Tests' + 'GitProjectHealth-Model-LocalImporter' ). spec group: 'default' with: #( 'Core' ) ] @@ -129,8 +130,8 @@ BaselineOfGitLabHealth >> defineJiraConnector: spec [ { #category : #baselines } BaselineOfGitLabHealth >> definePackages: spec [ - "generic" + spec package: 'GitProjectHealth-Model-Importer' with: [ spec requires: #( 'GitLabHealth-Model' ) ]. @@ -178,6 +179,12 @@ BaselineOfGitLabHealth >> definePackages: spec [ package: 'GitHubHealth-Model-Importer-Tests' with: [ spec requires: #( 'GitHubHealth-Model-Importer' ) ]. + "local" + + spec package: 'GitProjectHealth-Model-LocalImporter' with: [ + spec requires: + #( 'GitProjectHealth-Model-Importer' + 'GitLabHealth-Model-Extension' ) ]. "model extension" spec From f830c33a7738e987e2f04f2c9187632958c0d3c2 Mon Sep 17 00:00:00 2001 From: Benoit Verhaeghe Date: Fri, 7 Aug 2026 09:20:51 +0200 Subject: [PATCH 11/75] optimize load diff organize code load branches locally prepare load files --- .../GithubModelImporter.class.st | 2 +- .../OrderedCollection.extension.st | 8 +- .../GitlabModelImporter.class.st | 2 +- .../GitModelImporter.class.st | 43 ++++++++--- .../GitLocalModelImporter.class.st | 76 +++++++++++++++++++ 5 files changed, 119 insertions(+), 12 deletions(-) diff --git a/src/GitHubHealth-Model-Importer/GithubModelImporter.class.st b/src/GitHubHealth-Model-Importer/GithubModelImporter.class.st index 17baa0b5..d58bb1a5 100644 --- a/src/GitHubHealth-Model-Importer/GithubModelImporter.class.st +++ b/src/GitHubHealth-Model-Importer/GithubModelImporter.class.st @@ -379,7 +379,7 @@ GithubModelImporter >> importBranchesOf: aGLHProject [ ^ branches ] -{ #category : #'import - repositories' } +{ #category : #'import - branches' } GithubModelImporter >> importBranchesOfProject: aGLHProject [ "add the pipeline (actions runs) in the project" diff --git a/src/GitLabHealth-Model-Extension/OrderedCollection.extension.st b/src/GitLabHealth-Model-Extension/OrderedCollection.extension.st index 2b196622..5a1ee2d5 100644 --- a/src/GitLabHealth-Model-Extension/OrderedCollection.extension.st +++ b/src/GitLabHealth-Model-Extension/OrderedCollection.extension.st @@ -1,5 +1,11 @@ Extension { #name : #OrderedCollection } +{ #category : #'*GitLabHealth-Model-Extension' } +OrderedCollection >> add: anElement keyedBy: aKeyBlock [ + + ^ (self addAll: { anElement } keyedBy: aKeyBlock) first +] + { #category : #'*GitLabHealth-Model-Extension' } OrderedCollection >> add: anElmt unless: aConditionAsBlock [ "()>>>" @@ -13,7 +19,7 @@ OrderedCollection >> addAll: anElmtCollection keyedBy: aKeyBlock [ | elementByKey result | elementByKey := Dictionary new: self size + anElmtCollection size. - result := self class new: anElmtCollection size. + result := OrderedCollection new: anElmtCollection size. "Indexe les éléments déjà présents. En cas de doublon existant, conserve le premier." diff --git a/src/GitLabHealth-Model-Importer/GitlabModelImporter.class.st b/src/GitLabHealth-Model-Importer/GitlabModelImporter.class.st index 16953646..b5c5eaf8 100644 --- a/src/GitLabHealth-Model-Importer/GitlabModelImporter.class.st +++ b/src/GitLabHealth-Model-Importer/GitlabModelImporter.class.st @@ -1129,7 +1129,7 @@ GitlabModelImporter >> importFileWithPath: aPath ofProject: aGLHProject inBranch ^ self repoApi repositories getRawFile: aPath ofProject: aGLHProject id withParams: {#ref -> aBranch name} asDictionary . ] -{ #category : #'import - repositories' } +{ #category : #'import - files' } GitlabModelImporter >> importFilesOfBranch: aBranch [ | result files apiFiles params | diff --git a/src/GitProjectHealth-Model-Importer/GitModelImporter.class.st b/src/GitProjectHealth-Model-Importer/GitModelImporter.class.st index 0d2cc2a6..0bc1242b 100644 --- a/src/GitProjectHealth-Model-Importer/GitModelImporter.class.st +++ b/src/GitProjectHealth-Model-Importer/GitModelImporter.class.st @@ -447,7 +447,7 @@ GitModelImporter >> importAuthorOfCommit: aGLHCommit [ self subclassResponsibility ] -{ #category : #'import - repositories' } +{ #category : #'import - branches' } GitModelImporter >> importBranchesOf: aGLHProject [ self @@ -459,12 +459,18 @@ GitModelImporter >> importBranchesOf: aGLHProject [ ^ self subclassResponsibility ] -{ #category : #'import - repositories' } +{ #category : #'import - branches' } GitModelImporter >> importBranchesOfProject: aGLHProject [ ^ self subclassResponsibility ] +{ #category : #'import - branches' } +GitModelImporter >> importBranchesOfRepository: aGLHRepository [ + + ^ self subclassResponsibility +] + { #category : #'import - commits' } GitModelImporter >> importCommit: aCommitID ofProject: aGLHProject [ @@ -520,12 +526,22 @@ GitModelImporter >> importDiffRangesForDiff: aGLHDiff [ diffRanges := self glhModel addAll: (self parseDiffString: aGLHDiff) - unless: self blockForDiffRangeEquality. + keyedBy: [ :el | + { + el diff mooseID. + el originalLineRange. + el newLineRange } ]. ^ aGLHDiff diffRanges addAll: diffRanges unless: self blockForDiffRangeEquality ] +{ #category : #'import - files' } +GitModelImporter >> importFilesOfBranch: aGLHBranch [ + + ^ self subclassResponsibility +] + { #category : #'import - groups' } GitModelImporter >> importGroup: aGroupID [ @@ -672,6 +688,13 @@ GitModelImporter >> importRelease: aReleaseID ofProject: aGLHProject [ self subclassResponsibility ] +{ #category : #'import - repositories' } +GitModelImporter >> importRepository: aGLHRepository [ + "Repository is about git, not the social platform directly" + + ^ self subclassResponsibility +] + { #category : #'import - tags' } GitModelImporter >> importTagsForProject: aGLHProject [ self subclassResponsibility @@ -747,17 +770,19 @@ GitModelImporter >> parseDiffString: aDiff [ (line trimLeft beginsWith: #'@@') ifTrue: [ range := GLHDiffRange newFrom: line. firstChangeRange := true. - range := aDiff diffRanges add: range unless: [ :existing :new | - existing originalLineRange = new originalLineRange and: [ - existing newLineRange = new newLineRange ] ] ]. + range := aDiff diffRanges add: range keyedBy: [ :val | + { + val originalLineRange. + val newLineRange } ] ]. self withCommitDiffs ifTrue: [ firstChangeRange ifFalse: [ aChange := GLHChange newFrom: line. aChange relativeIndex: index. - aChange := range changes add: aChange unless: [ :existing :new | - existing sourceCode = new sourceCode and: [ - existing index = new index ] ]. + aChange := range changes add: aChange keyedBy: [ :el | + { + el index. + el sourceCode } ]. index := index + 1 ] ] ]. "add changes to the diff range" diff --git a/src/GitProjectHealth-Model-LocalImporter/GitLocalModelImporter.class.st b/src/GitProjectHealth-Model-LocalImporter/GitLocalModelImporter.class.st index 9d928a95..d40a7b43 100644 --- a/src/GitProjectHealth-Model-LocalImporter/GitLocalModelImporter.class.st +++ b/src/GitProjectHealth-Model-LocalImporter/GitLocalModelImporter.class.st @@ -36,6 +36,42 @@ GitLocalModelImporter >> gitRepository [ yourself ] +{ #category : #accessing } +GitLocalModelImporter >> gitRepositoryOf: aGLHRepository [ + + ^ aGLHRepository cacheAt: #localImporterGitReference ifAbsentPut: [ + (LGitRepository on: (aGLHRepository + cacheAt: #localImporterReference + ifAbsent: [ + Error signal: 'Local GLHRepository without importer reference' ])) + init; + yourself ] +] + +{ #category : #'import - branches' } +GitLocalModelImporter >> importBranchesOfRepository: aGLHRepository [ + + | branches | + branches := (self gitRepositoryOf: aGLHRepository) allBranches + select: [ :branch | branch isBranch ] + thenCollect: [ :branch | + GLHBranch new + name: branch shortName; + sha: branch targetId hexString; + yourself ]. + aGLHRepository branches addAll: branches. + self glhModel addAll: branches keyedBy: [ :el | + { + el name. + el repository } ]. + + self withFiles ifTrue: [ + branches do: [ :branch | self importFilesOfBranch: branch ] ]. + + + ^ branches +] + { #category : #'import - commits' } GitLocalModelImporter >> importCommit: aLGitCommit [ @@ -50,6 +86,34 @@ GitLocalModelImporter >> importCommit: aLGitCommit [ yourself ] +{ #category : #'import - commits' } +GitLocalModelImporter >> importCommitsOfProject: aProject since: sinceDate until: untilDate [ + + | i revwalk commits | + i := 0. + revwalk := LGitRevwalk of: self gitRepository. + commits := OrderedCollection new. + revwalk pushHead. + [ + revwalk hasNext and: [ + commits isEmpty or: [ + commits last committed_date > sinceDate ] ] ] whileTrue: [ + | gitCommit glhCommit | + i := i + 1. + gitCommit := revwalk next. + glhCommit := self importCommit: gitCommit. + commits add: glhCommit. + glhCommit cacheAt: #importerOrigin put: gitCommit ]. + + self chainsCommitsFrom: commits. + + self withCommitDiffs ifTrue: [ + commits do: [ :commit | self importDiffOfCommit: commit ] ]. + + + ^ commits +] + { #category : #'import - diffs' } GitLocalModelImporter >> importDiffOfCommit: commit [ @@ -75,6 +139,12 @@ GitLocalModelImporter >> importDiffOfCommit: commit [ ^ commit diffs ] +{ #category : #'import - files' } +GitLocalModelImporter >> importFilesOfBranch: aGLHBranch [ + + ^ self halt +] + { #category : #'import - commits' } GitLocalModelImporter >> importLatestCommitsOfProject [ @@ -104,6 +174,12 @@ GitLocalModelImporter >> importLatestCommitsOfProject: aGLHProject [ self importLatestCommitsOfProject ] +{ #category : #'import - repositories' } +GitLocalModelImporter >> importRepository: aGLHRepository [ + + self importBranchesOfRepository: aGLHRepository. +] + { #category : #initialization } GitLocalModelImporter >> initialize [ From 362b71285bace5d750818e23f5ec37e942892fbe Mon Sep 17 00:00:00 2001 From: Benoit Verhaeghe Date: Fri, 7 Aug 2026 10:10:28 +0200 Subject: [PATCH 12/75] can import files of branches --- .../GitlabModelImporter.class.st | 4 +-- .../GitLocalModelImporter.class.st | 25 ++++++++++++++++++- 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/src/GitLabHealth-Model-Importer/GitlabModelImporter.class.st b/src/GitLabHealth-Model-Importer/GitlabModelImporter.class.st index b5c5eaf8..456a1864 100644 --- a/src/GitLabHealth-Model-Importer/GitlabModelImporter.class.st +++ b/src/GitLabHealth-Model-Importer/GitlabModelImporter.class.st @@ -1097,7 +1097,7 @@ GitlabModelImporter >> importDiffOfMergeRequest: aMergeRequest [ ^ aMergeRequest diffs ] -{ #category : #'import - repositories' } +{ #category : #'import - files' } GitlabModelImporter >> importDirectoryFiles: aDirectoryFile OfBranch: aBranch [ | result files apiFiles params | @@ -1123,7 +1123,7 @@ GitlabModelImporter >> importDirectoryFiles: aDirectoryFile OfBranch: aBranch [ self importDirectoryFiles: file OfBranch: aBranch ] ] -{ #category : #'import - file' } +{ #category : #'import - files' } GitlabModelImporter >> importFileWithPath: aPath ofProject: aGLHProject inBranch: aBranch [ self flag: 'imported file need to be store inside the model'. ^ self repoApi repositories getRawFile: aPath ofProject: aGLHProject id withParams: {#ref -> aBranch name} asDictionary . diff --git a/src/GitProjectHealth-Model-LocalImporter/GitLocalModelImporter.class.st b/src/GitProjectHealth-Model-LocalImporter/GitLocalModelImporter.class.st index d40a7b43..5ba98640 100644 --- a/src/GitProjectHealth-Model-LocalImporter/GitLocalModelImporter.class.st +++ b/src/GitProjectHealth-Model-LocalImporter/GitLocalModelImporter.class.st @@ -11,6 +11,21 @@ Class { #category : #'GitProjectHealth-Model-LocalImporter' } +{ #category : #'import - files' } +GitLocalModelImporter >> buildGPHFileFor: aDiskDirectoryEntry [ + + | file | + file := (aDiskDirectoryEntry isDirectory + ifTrue: [ GLHFileDirectory ] + ifFalse: [ GLHFileBlob ]) new. + self glhModel add: file. + file name: aDiskDirectoryEntry basename. + (file isKindOf: GLHFileDirectory) ifTrue: [ + file files: (aDiskDirectoryEntry children collect: [ :child | + self buildGPHFileFor: child ]) ]. + ^ file +] + { #category : #'private - diffs' } GitLocalModelImporter >> createDiffFrom: patch and: delta [ @@ -141,8 +156,16 @@ GitLocalModelImporter >> importDiffOfCommit: commit [ { #category : #'import - files' } GitLocalModelImporter >> importFilesOfBranch: aGLHBranch [ + + | gitBranch | + gitRepository := self gitRepositoryOf: aGLHBranch repository. + gitBranch := gitRepository allBranches detect: [ :branch | + branch shortName = aGLHBranch name ]. + gitRepository resetHardTo: gitBranch object. + aGLHBranch files: (gitRepository workingDirectory children + reject: [ :child | child basename = '.git' ] + thenCollect: [ :child | self buildGPHFileFor: child ]) - ^ self halt ] { #category : #'import - commits' } From 7dc4f575d0ce67ad2d42cdb554f5f7c8c51e626a Mon Sep 17 00:00:00 2001 From: Benoit Verhaeghe Date: Fri, 7 Aug 2026 11:39:49 +0200 Subject: [PATCH 13/75] link commit diff files to file if possible --- .../GitLocalModelImporter.class.st | 61 ++++++++++++++----- 1 file changed, 46 insertions(+), 15 deletions(-) diff --git a/src/GitProjectHealth-Model-LocalImporter/GitLocalModelImporter.class.st b/src/GitProjectHealth-Model-LocalImporter/GitLocalModelImporter.class.st index 5ba98640..15c02d4b 100644 --- a/src/GitProjectHealth-Model-LocalImporter/GitLocalModelImporter.class.st +++ b/src/GitProjectHealth-Model-LocalImporter/GitLocalModelImporter.class.st @@ -66,7 +66,8 @@ GitLocalModelImporter >> gitRepositoryOf: aGLHRepository [ { #category : #'import - branches' } GitLocalModelImporter >> importBranchesOfRepository: aGLHRepository [ - | branches | + | branches headCommit | + headCommit := (self gitRepositoryOf: aGLHRepository) head object. branches := (self gitRepositoryOf: aGLHRepository) allBranches select: [ :branch | branch isBranch ] thenCollect: [ :branch | @@ -82,7 +83,7 @@ GitLocalModelImporter >> importBranchesOfRepository: aGLHRepository [ self withFiles ifTrue: [ branches do: [ :branch | self importFilesOfBranch: branch ] ]. - + (self gitRepositoryOf: aGLHRepository) resetHardTo: headCommit. ^ branches ] @@ -104,27 +105,54 @@ GitLocalModelImporter >> importCommit: aLGitCommit [ { #category : #'import - commits' } GitLocalModelImporter >> importCommitsOfProject: aProject since: sinceDate until: untilDate [ + self importCommitsOfRepository: aProject repository since: sinceDate until: untilDate +] + +{ #category : #'import - repositories' } +GitLocalModelImporter >> importCommitsOfRepository: aGLHRepository [ + + ^ self + importCommitsOfRepository: aGLHRepository + since: withCommitsSince + until: nil +] + +{ #category : #'import - commits' } +GitLocalModelImporter >> importCommitsOfRepository: aGPHRepository since: sinceDate until: untilDate [ + | i revwalk commits | i := 0. - revwalk := LGitRevwalk of: self gitRepository. + revwalk := LGitRevwalk of: (self gitRepositoryOf: aGPHRepository). commits := OrderedCollection new. revwalk pushHead. [ revwalk hasNext and: [ - commits isEmpty or: [ - commits last committed_date > sinceDate ] ] ] whileTrue: [ - | gitCommit glhCommit | - i := i + 1. - gitCommit := revwalk next. - glhCommit := self importCommit: gitCommit. - commits add: glhCommit. - glhCommit cacheAt: #importerOrigin put: gitCommit ]. + commits isEmpty or: [ commits last committed_date > sinceDate ] ] ] + whileTrue: [ + | gitCommit glhCommit | + i := i + 1. + gitCommit := revwalk next. + glhCommit := self importCommit: gitCommit. + commits add: glhCommit. + glhCommit cacheAt: #importerOrigin put: gitCommit ]. self chainsCommitsFrom: commits. self withCommitDiffs ifTrue: [ - commits do: [ :commit | self importDiffOfCommit: commit ] ]. - + | diffs | + diffs := commits flatCollect: [ :commit | + self importDiffOfCommit: commit ]. + self withFiles ifTrue: [ + | indexedFiles | + indexedFiles := (aGPHRepository allToScope: GLHFile) + collect: [ :el | el path -> el ] + as: Dictionary. + + diffs do: [ :diff | + indexedFiles + at: diff new_path + ifPresent: [ :gphFile | diff addFile: gphFile ] + ifAbsent: [ "not supported yet" ] ] ] ]. ^ commits ] @@ -148,9 +176,11 @@ GitLocalModelImporter >> importDiffOfCommit: commit [ delta := gitDiff deltas at: idx. patch := gitDiff patchAt: idx. self createDiffFrom: patch and: delta ]) ]. - "changes are added into the model during the import" - commit diffs do: [ :diff | self importDiffRangesForDiff: diff ]. + commit diffs do: [ :diff | + | ranges | + ranges := self importDiffRangesForDiff: diff ]. + ^ commit diffs ] @@ -201,6 +231,7 @@ GitLocalModelImporter >> importLatestCommitsOfProject: aGLHProject [ GitLocalModelImporter >> importRepository: aGLHRepository [ self importBranchesOfRepository: aGLHRepository. + self importCommitsOfRepository: aGLHRepository ] { #category : #initialization } From b78c2596f57df1fd695ebb249e3b2204149f9a14 Mon Sep 17 00:00:00 2001 From: Benoit Verhaeghe Date: Mon, 10 Aug 2026 15:37:09 +0200 Subject: [PATCH 14/75] begin work on DOA --- .../GLPDegreeOfAuthorshipAnalyzer.class.st | 88 +++++++++++++++++++ .../GitLocalModelImporter.class.st | 52 +++++++++-- 2 files changed, 131 insertions(+), 9 deletions(-) create mode 100644 src/GitLabHealth-Model-Analysis/GLPDegreeOfAuthorshipAnalyzer.class.st diff --git a/src/GitLabHealth-Model-Analysis/GLPDegreeOfAuthorshipAnalyzer.class.st b/src/GitLabHealth-Model-Analysis/GLPDegreeOfAuthorshipAnalyzer.class.st new file mode 100644 index 00000000..faab9513 --- /dev/null +++ b/src/GitLabHealth-Model-Analysis/GLPDegreeOfAuthorshipAnalyzer.class.st @@ -0,0 +1,88 @@ +" +Based on the paper [A Novel Approach for Estimating Truck Factors](https://arxiv.org/pdf/1604.06766), I compute the DOA between a file and a author based on prexisting computed data by GitProjectHealth. + +Here is the algorithm: + +```language=txt +DOA(md, fp) = 3.293 + 1.098 ×FA(md, fp) + 0.164× DL(md, fp)−0.321 ×ln(1 + AC (md, fp)) + +From the equation, DOA depends on three factors: (i) first authorship (FA): if md originally created f, FA is 1; otherwise it is 0; (ii) number of deliveries (DL): number of changes in f made by md; and (iii) number of acceptances (AC): number of changes in f made by any developer, except md. +``` + +Magic numbers were computed by the authors. + +The paper [Algorithms for Estimating Truck Factors: A Comparative study](https://homepages.dcc.ufmg.br/~mtov/pub/2019-sqj.pdf) consider AVL algorithm (on which is based DOA) correct. So DOA seems good + +## Usage + +```st + +GLPDegreeOfAuthorshipAnalyzer new + glhFile: glhFile; + glhUser: glhUser; + compute +``` +" +Class { + #name : #GLPDegreeOfAuthorshipAnalyzer, + #superclass : #Object, + #instVars : [ + 'glhFile', + 'glhUser' + ], + #category : #'GitLabHealth-Model-Analysis' +} + +{ #category : #accessing } +GLPDegreeOfAuthorshipAnalyzer >> authorCreatedFile [ + "Paper: Should be 1 if yes. 0 if not" + + "Us: we consider that files might be old, and so in this case 0" + ^ 0 +] + +{ #category : #accessing } +GLPDegreeOfAuthorshipAnalyzer >> compute [ + + ^ 3.293 + (1.098 * self authorCreatedFile) + + (0.164 * self numberOfChageMadeByOriginalAuthorDL) + - (0.321 * (1 + self numberOfChangeMadeByOtherAuthors) ln) +] + +{ #category : #accessing } +GLPDegreeOfAuthorshipAnalyzer >> glhFile [ + + ^ glhFile +] + +{ #category : #accessing } +GLPDegreeOfAuthorshipAnalyzer >> glhFile: anObject [ + + glhFile := anObject +] + +{ #category : #accessing } +GLPDegreeOfAuthorshipAnalyzer >> glhUser [ + + ^ glhUser +] + +{ #category : #accessing } +GLPDegreeOfAuthorshipAnalyzer >> glhUser: anObject [ + + glhUser := anObject +] + +{ #category : #accessing } +GLPDegreeOfAuthorshipAnalyzer >> numberOfChageMadeByOriginalAuthorDL [ + + self flag: #todo. + ^ 1 +] + +{ #category : #accessing } +GLPDegreeOfAuthorshipAnalyzer >> numberOfChangeMadeByOtherAuthors [ + + self flag: #todo. + ^ 1 +] diff --git a/src/GitProjectHealth-Model-LocalImporter/GitLocalModelImporter.class.st b/src/GitProjectHealth-Model-LocalImporter/GitLocalModelImporter.class.st index 15c02d4b..df2dffa9 100644 --- a/src/GitProjectHealth-Model-LocalImporter/GitLocalModelImporter.class.st +++ b/src/GitProjectHealth-Model-LocalImporter/GitLocalModelImporter.class.st @@ -91,15 +91,21 @@ GitLocalModelImporter >> importBranchesOfRepository: aGLHRepository [ { #category : #'import - commits' } GitLocalModelImporter >> importCommit: aLGitCommit [ - ^ self glhModel newCommit - id: aLGitCommit name; - message: aLGitCommit message; - committed_date: aLGitCommit time asDateAndTime; - committer_email: aLGitCommit author email; - committer_name: aLGitCommit author name; - parent_ids: - (aLGitCommit parents collect: [ :parent | parent name ]); - yourself + | commit | + commit := self glhModel newCommit + id: aLGitCommit name; + message: aLGitCommit message; + committed_date: aLGitCommit time asDateAndTime; + committer_email: aLGitCommit author email; + committer_name: aLGitCommit author name; + authored_date: aLGitCommit time asDateAndTime; + author_email: aLGitCommit author email; + author_name: aLGitCommit author name; + parent_ids: + (aLGitCommit parents collect: [ :parent | parent name ]); + yourself. + self resolveCreatorOfCommit: commit. + ^ commit ] { #category : #'import - commits' } @@ -234,6 +240,25 @@ GitLocalModelImporter >> importRepository: aGLHRepository [ self importCommitsOfRepository: aGLHRepository ] +{ #category : #'import - commits' } +GitLocalModelImporter >> importUserByCommit: aGLHCommit [ + + ^ self userCatalogue collectUsernames + at: aGLHCommit author_name + ifAbsent: [ + | glhUser | + glhUser := GLHUser new + id: aGLHCommit author_name; + username: aGLHCommit author_name; + name: aGLHCommit author_name; + email: aGLHCommit author_email; + yourself. + + self glhModel add: glhUser unless: self blockOnIdEquality. + self userCatalogue addUser: glhUser withName: glhUser username. + glhUser ] +] + { #category : #initialization } GitLocalModelImporter >> initialize [ @@ -246,3 +271,12 @@ GitLocalModelImporter >> localGitRepository: aFileReference [ localGitRepositoryFileReference := aFileReference ] + +{ #category : #'import - commits' } +GitLocalModelImporter >> resolveCreatorOfCommit: aGLHCommit [ + aGLHCommit commitCreator ifNil: [ + aGLHCommit commitCreator: + (self importUserByCommit: aGLHCommit) ]. + + ^ aGLHCommit commitCreator +] From 505582509022db8acef9091fd4465ffa50d0bd9f Mon Sep 17 00:00:00 2001 From: Benoit Verhaeghe Date: Mon, 10 Aug 2026 15:44:50 +0200 Subject: [PATCH 15/75] A file can be referenced by several diffs (when we have several commits, that have several diffs on the same files) --- .../GLHMetamodelGenerator.class.st | 2 +- src/GitLabHealth-Model/GLHDiff.class.st | 9 +------ src/GitLabHealth-Model/GLHFile.class.st | 26 ++++++++++--------- src/GitLabHealth-Model/GLHUser.class.st | 2 +- .../RSCommit.class.st | 2 +- 5 files changed, 18 insertions(+), 23 deletions(-) diff --git a/src/GitLabHealth-Model-Generator/GLHMetamodelGenerator.class.st b/src/GitLabHealth-Model-Generator/GLHMetamodelGenerator.class.st index 81d0290c..8c3fb642 100644 --- a/src/GitLabHealth-Model-Generator/GLHMetamodelGenerator.class.st +++ b/src/GitLabHealth-Model-Generator/GLHMetamodelGenerator.class.st @@ -292,7 +292,7 @@ GLHMetamodelGenerator >> defineRelations [ (pipeline property: #jobs) <>-* (job property: #pipeline). (pipeline property: #user) *- (user property: #launchedPipelines). (user property: #jobs) <>-* (job property: #user). - (diff property: #file) <>-* (file property: #diffs). + (diff property: #file) *-* (file property: #diffs). (diff property: #diffRanges) <>-* (diffRange property: #diff). (diffRange property: #changes) -* (change property: #diffRange). diff --git a/src/GitLabHealth-Model/GLHDiff.class.st b/src/GitLabHealth-Model/GLHDiff.class.st index ad9c9172..c0f89837 100644 --- a/src/GitLabHealth-Model/GLHDiff.class.st +++ b/src/GitLabHealth-Model/GLHDiff.class.st @@ -13,11 +13,11 @@ The diff of a commit | Relation | Origin | Opposite | Type | Comment | |---| | `diffRanges` | `GLHDiff` | `diff` | `GLHDiffRange` | | -| `file` | `GLHDiff` | `diffs` | `GLHFile` | | ### Other | Relation | Origin | Opposite | Type | Comment | |---| +| `file` | `GLHDiff` | `diffs` | `GLHFile` | | | `mergeRequest` | `GLHDiff` | `diffs` | `GLHMergeRequest` | | @@ -168,13 +168,6 @@ GLHDiff >> file: anObject [ file value: anObject ] -{ #category : #navigation } -GLHDiff >> fileGroup [ - - - ^ MooseSpecializedGroup withAll: self file asSet -] - { #category : #accessing } GLHDiff >> mergeRequest [ "Relation named: #mergeRequest type: #GLHMergeRequest opposite: #diffs" diff --git a/src/GitLabHealth-Model/GLHFile.class.st b/src/GitLabHealth-Model/GLHFile.class.st index c193f4d3..921217ed 100644 --- a/src/GitLabHealth-Model/GLHFile.class.st +++ b/src/GitLabHealth-Model/GLHFile.class.st @@ -8,9 +8,13 @@ A file | Relation | Origin | Opposite | Type | Comment | |---| | `branch` | `GLHFile` | `files` | `GLHBranch` | | -| `diffs` | `GLHFile` | `file` | `GLHDiff` | | | `directoryOwner` | `GLHFile` | `files` | `GLHFileDirectory` | | +### Other +| Relation | Origin | Opposite | Type | Comment | +|---| +| `diffs` | `GLHFile` | `file` | `GLHDiff` | | + ## Properties ====================== @@ -24,10 +28,10 @@ Class { #name : #GLHFile, #superclass : #GLHEntity, #instVars : [ - '#name => FMProperty', '#branch => FMOne type: #GLHBranch opposite: #files', + '#diffs => FMMany type: #GLHDiff opposite: #file', '#directoryOwner => FMOne type: #GLHFileDirectory opposite: #files', - '#diffs => FMOne type: #GLHDiff opposite: #file' + '#name => FMProperty' ], #category : #'GitLabHealth-Model-Entities' } @@ -41,6 +45,12 @@ GLHFile class >> annotation [ ^ self ] +{ #category : #adding } +GLHFile >> addDiff: anObject [ + + ^ self diffs add: anObject +] + { #category : #accessing } GLHFile >> branch [ "Relation named: #branch type: #GLHBranch opposite: #files" @@ -69,7 +79,6 @@ GLHFile >> diffs [ "Relation named: #diffs type: #GLHDiff opposite: #file" - ^ diffs ] @@ -77,14 +86,7 @@ GLHFile >> diffs [ GLHFile >> diffs: anObject [ - diffs := anObject -] - -{ #category : #navigation } -GLHFile >> diffsGroup [ - - - ^ MooseSpecializedGroup with: self diffs + diffs value: anObject ] { #category : #accessing } diff --git a/src/GitLabHealth-Model/GLHUser.class.st b/src/GitLabHealth-Model/GLHUser.class.st index d6648af4..20a41c87 100644 --- a/src/GitLabHealth-Model/GLHUser.class.st +++ b/src/GitLabHealth-Model/GLHUser.class.st @@ -51,8 +51,8 @@ A GitLab User | `job_title` | `String` | nil | | | `linkedin` | `String` | nil | | | `location` | `String` | nil | | -| `name` | `String` | nil | Basic name of the entity, not full reference.| | `name` | `String` | nil | | +| `name` | `String` | nil | Basic name of the entity, not full reference.| | `organization` | `String` | nil | | | `pronouns` | `String` | nil | | | `public_email` | `String` | nil | | diff --git a/src/GitLabHealth-Visualization/RSCommit.class.st b/src/GitLabHealth-Visualization/RSCommit.class.st index 5637dacf..8f5b8834 100644 --- a/src/GitLabHealth-Visualization/RSCommit.class.st +++ b/src/GitLabHealth-Visualization/RSCommit.class.st @@ -140,7 +140,7 @@ RSCommit >> buildCommitDiffFileImpact [ shapes := RSGroup new. - aCommit diffsGroup do: [ :diff | + aCommit diffs do: [ :diff | shapes add: (RSCommitDiff new shapeForDiffRange: diff From 2fe0843d1b799fee6089425b45e5e6041b8e5638 Mon Sep 17 00:00:00 2001 From: Benoit Verhaeghe Date: Mon, 10 Aug 2026 16:26:46 +0200 Subject: [PATCH 16/75] poc finished --- .../GLHFile.extension.st | 18 ++++ .../GLPDegreeOfAuthorshipAnalyzer.class.st | 87 +++++++---------- ...GLPUserDegreeOfAuthorshipAnalyzer.class.st | 97 +++++++++++++++++++ .../String.extension.st | 63 ++++++------ 4 files changed, 180 insertions(+), 85 deletions(-) create mode 100644 src/GitLabHealth-Model-Analysis/GLHFile.extension.st create mode 100644 src/GitLabHealth-Model-Analysis/GLPUserDegreeOfAuthorshipAnalyzer.class.st diff --git a/src/GitLabHealth-Model-Analysis/GLHFile.extension.st b/src/GitLabHealth-Model-Analysis/GLHFile.extension.st new file mode 100644 index 00000000..6c0a0fef --- /dev/null +++ b/src/GitLabHealth-Model-Analysis/GLHFile.extension.st @@ -0,0 +1,18 @@ +Extension { #name : #GLHFile } + +{ #category : #'*GitLabHealth-Model-Analysis' } +GLHFile >> degreeOfAuthorship [ + + ^ GLPDegreeOfAuthorshipAnalyzer new + glhFile: self; + compute +] + +{ #category : #'*GitLabHealth-Model-Analysis' } +GLHFile >> degreeOfAuthorshipAuthors [ + "the most restrictive computation" + + ^ GLPDegreeOfAuthorshipAnalyzer new + glhFile: self; + computeFileAuthors +] diff --git a/src/GitLabHealth-Model-Analysis/GLPDegreeOfAuthorshipAnalyzer.class.st b/src/GitLabHealth-Model-Analysis/GLPDegreeOfAuthorshipAnalyzer.class.st index faab9513..20e0a97b 100644 --- a/src/GitLabHealth-Model-Analysis/GLPDegreeOfAuthorshipAnalyzer.class.st +++ b/src/GitLabHealth-Model-Analysis/GLPDegreeOfAuthorshipAnalyzer.class.st @@ -1,88 +1,67 @@ " -Based on the paper [A Novel Approach for Estimating Truck Factors](https://arxiv.org/pdf/1604.06766), I compute the DOA between a file and a author based on prexisting computed data by GitProjectHealth. +Given a `GLHFile` I provide the normalized DOA values with their user. +I also provide an utility that filters the one considered as real author based on k = 0.75 -Here is the algorithm: - -```language=txt -DOA(md, fp) = 3.293 + 1.098 ×FA(md, fp) + 0.164× DL(md, fp)−0.321 ×ln(1 + AC (md, fp)) - -From the equation, DOA depends on three factors: (i) first authorship (FA): if md originally created f, FA is 1; otherwise it is 0; (ii) number of deliveries (DL): number of changes in f made by md; and (iii) number of acceptances (AC): number of changes in f made by any developer, except md. -``` - -Magic numbers were computed by the authors. - -The paper [Algorithms for Estimating Truck Factors: A Comparative study](https://homepages.dcc.ufmg.br/~mtov/pub/2019-sqj.pdf) consider AVL algorithm (on which is based DOA) correct. So DOA seems good - -## Usage - -```st - -GLPDegreeOfAuthorshipAnalyzer new - glhFile: glhFile; - glhUser: glhUser; - compute -``` +(value comes from the [A Novel Approach for Estimating Truck Factors](https://arxiv.org/pdf/1604.06766) research paper) " Class { #name : #GLPDegreeOfAuthorshipAnalyzer, #superclass : #Object, #instVars : [ 'glhFile', - 'glhUser' + 'rawResults', + 'normalizedResults' ], - #category : #'GitLabHealth-Model-Analysis' + #category : #'GitLabHealth-Model-Analysis-TruckFactor' } { #category : #accessing } -GLPDegreeOfAuthorshipAnalyzer >> authorCreatedFile [ - "Paper: Should be 1 if yes. 0 if not" +GLPDegreeOfAuthorshipAnalyzer >> authorshipThreshold [ - "Us: we consider that files might be old, and so in this case 0" - ^ 0 + ^ 0.75 ] { #category : #accessing } -GLPDegreeOfAuthorshipAnalyzer >> compute [ +GLPDegreeOfAuthorshipAnalyzer >> baseKnowledge [ - ^ 3.293 + (1.098 * self authorCreatedFile) - + (0.164 * self numberOfChageMadeByOriginalAuthorDL) - - (0.321 * (1 + self numberOfChangeMadeByOtherAuthors) ln) + ^ 3.293 ] { #category : #accessing } -GLPDegreeOfAuthorshipAnalyzer >> glhFile [ - - ^ glhFile -] - -{ #category : #accessing } -GLPDegreeOfAuthorshipAnalyzer >> glhFile: anObject [ - - glhFile := anObject -] - -{ #category : #accessing } -GLPDegreeOfAuthorshipAnalyzer >> glhUser [ +GLPDegreeOfAuthorshipAnalyzer >> compute [ - ^ glhUser + | authors | + authors := self glhFile diffs + collect: [ :diff | diff commit commitCreator ] + as: Set. + rawResults := (self glhFile mooseModel allWithType: GLHUser) + collect: [ :author | + author -> (GLPUserDegreeOfAuthorshipAnalyzer new + glhFile: self glhFile; + glhUser: author; + compute) ] + as: Dictionary. + normalizedResults := AIMinMaxNormalizer new normalize: rawResults. + ^ normalizedResults ] { #category : #accessing } -GLPDegreeOfAuthorshipAnalyzer >> glhUser: anObject [ +GLPDegreeOfAuthorshipAnalyzer >> computeFileAuthors [ + "we consider a developer as an author of a file if its resulting normalized DOA is greater than a threshold k and its absolute DOA is not lower than a value m." - glhUser := anObject + ^ self compute associationsSelect: [ :assoc | + assoc value >= self authorshipThreshold and: [ + (rawResults at: assoc key) >= self baseKnowledge ] ] ] { #category : #accessing } -GLPDegreeOfAuthorshipAnalyzer >> numberOfChageMadeByOriginalAuthorDL [ +GLPDegreeOfAuthorshipAnalyzer >> glhFile [ - self flag: #todo. - ^ 1 + ^ glhFile ] { #category : #accessing } -GLPDegreeOfAuthorshipAnalyzer >> numberOfChangeMadeByOtherAuthors [ +GLPDegreeOfAuthorshipAnalyzer >> glhFile: anObject [ - self flag: #todo. - ^ 1 + glhFile := anObject ] diff --git a/src/GitLabHealth-Model-Analysis/GLPUserDegreeOfAuthorshipAnalyzer.class.st b/src/GitLabHealth-Model-Analysis/GLPUserDegreeOfAuthorshipAnalyzer.class.st new file mode 100644 index 00000000..7136ca47 --- /dev/null +++ b/src/GitLabHealth-Model-Analysis/GLPUserDegreeOfAuthorshipAnalyzer.class.st @@ -0,0 +1,97 @@ +" +I am used by `GLPDegreeOfAuthorshipAnalyzer` + +Based on the paper [A Novel Approach for Estimating Truck Factors](https://arxiv.org/pdf/1604.06766), I compute the DOA between a file and a author based on prexisting computed data by GitProjectHealth. + +Here is the algorithm: + +```language=txt +DOA(md, fp) = 3.293 + 1.098 ×FA(md, fp) + 0.164× DL(md, fp)−0.321 ×ln(1 + AC (md, fp)) + +From the equation, DOA depends on three factors: (i) first authorship (FA): if md originally created f, FA is 1; otherwise it is 0; (ii) number of deliveries (DL): number of changes in f made by md; and (iii) number of acceptances (AC): number of changes in f made by any developer, except md. +``` + +Magic numbers were computed by the authors. + +The paper [Algorithms for Estimating Truck Factors: A Comparative study](https://homepages.dcc.ufmg.br/~mtov/pub/2019-sqj.pdf) consider AVL algorithm (on which is based DOA) correct. So DOA seems good + +## Usage + +```st + +GLPDegreeOfAuthorshipAnalyzer new + glhFile: glhFile; + glhUser: glhUser; + compute +``` +" +Class { + #name : #GLPUserDegreeOfAuthorshipAnalyzer, + #superclass : #Object, + #instVars : [ + 'glhFile', + 'glhUser', + 'baseKnowledge' + ], + #category : #'GitLabHealth-Model-Analysis-TruckFactor' +} + +{ #category : #accessing } +GLPUserDegreeOfAuthorshipAnalyzer >> authorCreatedFile [ + "Paper: Should be 1 if yes. 0 if not" + + "Us: we consider that files might be old, and so in this case 0" + ^ 0 +] + +{ #category : #accessing } +GLPUserDegreeOfAuthorshipAnalyzer >> compute [ + + ^ baseKnowledge + (1.098 * self authorCreatedFile) + + (0.164 * self numberOfChageMadeByOriginalAuthorDL) + - (0.321 * (1 + self numberOfChangeMadeByOtherAuthors) ln) +] + +{ #category : #accessing } +GLPUserDegreeOfAuthorshipAnalyzer >> glhFile [ + + ^ glhFile +] + +{ #category : #accessing } +GLPUserDegreeOfAuthorshipAnalyzer >> glhFile: anObject [ + + glhFile := anObject +] + +{ #category : #accessing } +GLPUserDegreeOfAuthorshipAnalyzer >> glhUser [ + + ^ glhUser +] + +{ #category : #accessing } +GLPUserDegreeOfAuthorshipAnalyzer >> glhUser: anObject [ + + glhUser := anObject +] + +{ #category : #initialization } +GLPUserDegreeOfAuthorshipAnalyzer >> initialize [ + super initialize. + baseKnowledge := 3.293 +] + +{ #category : #accessing } +GLPUserDegreeOfAuthorshipAnalyzer >> numberOfChageMadeByOriginalAuthorDL [ + + ^ self glhFile diffs count: [ :diff | + diff commit commitCreator = self glhUser ] +] + +{ #category : #accessing } +GLPUserDegreeOfAuthorshipAnalyzer >> numberOfChangeMadeByOtherAuthors [ + + ^ self glhFile diffs count: [ :diff | + diff commit commitCreator ~= self glhUser ] +] diff --git a/src/GitLabHealth-Model-Analysis/String.extension.st b/src/GitLabHealth-Model-Analysis/String.extension.st index d6be4dfd..bc675238 100644 --- a/src/GitLabHealth-Model-Analysis/String.extension.st +++ b/src/GitLabHealth-Model-Analysis/String.extension.st @@ -1,38 +1,39 @@ Extension { #name : #String } { #category : #'*GitLabHealth-Model-Analysis' } -String >> absLevenshteinDistanceWith: aString [ - ^ { self levenshteinDistanceWith: aString . - aString levenshteinDistanceWith: self } max. +String >> absLevenshteinDistanceWith: aString [ + + ^ { + (self levenshteinDistanceWith: aString). + (aString levenshteinDistanceWith: self) } max ] { #category : #'*GitLabHealth-Model-Analysis' } -String >> levenshteinDistanceWith: aString [ - | m n distances cost | - - m := self size. - n := aString size. - - "Initialize a 2D array (m+1 x n+1) to store distances" - distances := Array2D rows: m+1 columns: n+1 element: 0. - - "Fill base cases for first row and first column" - 1 to: m do: [ :i | distances at: i+1 at: 1 put: i ]. - 1 to: n do: [ :j | distances at: 1 at: j+1 put: j ]. - - - "Compute distances using dynamic programming" - 2 to: m do: [ :i | - 2 to: n do: [ :j | - cost := (self at: i) = (aString at: j) ifTrue: [ 0 ] ifFalse: [ 1 ]. - distances at: (i) at: (j) put: { - ((distances at: i-1 at: j) + 1) . - ((distances at: 1 at: j-1) + 1) . - ((distances at: i-1 at: j-1) + cost) . - } min . - ]. - ]. - - "Return the Levenshtein distance between the two strings" - ^ distances at: m at: n +String >> levenshteinDistanceWith: aString [ + + | m n distances cost | + m := self size. + n := aString size. + + "Initialize a 2D array (m+1 x n+1) to store distances" + distances := Array2D rows: m + 1 columns: n + 1 element: 0. + + "Fill base cases for first row and first column" + 1 to: m do: [ :i | distances at: i + 1 at: 1 put: i ]. + 1 to: n do: [ :j | distances at: 1 at: j + 1 put: j ]. + + + "Compute distances using dynamic programming" + 2 to: m do: [ :i | + 2 to: n do: [ :j | + cost := (self at: i) = (aString at: j) + ifTrue: [ 0 ] + ifFalse: [ 1 ]. + distances at: i at: j put: { + ((distances at: i - 1 at: j) + 1). + ((distances at: 1 at: j - 1) + 1). + ((distances at: i - 1 at: j - 1) + cost) } min ] ]. + + "Return the Levenshtein distance between the two strings" + ^ distances at: m at: n ] From 34b0b9fdf6a2d06496526fd489c6806c3fec80c8 Mon Sep 17 00:00:00 2001 From: Benoit Verhaeghe Date: Tue, 11 Aug 2026 10:19:45 +0200 Subject: [PATCH 17/75] handle error --- src/GitProjectHealth-Model-Importer/GitModelImporter.class.st | 2 +- .../GitLocalModelImporter.class.st | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/GitProjectHealth-Model-Importer/GitModelImporter.class.st b/src/GitProjectHealth-Model-Importer/GitModelImporter.class.st index 0bc1242b..2c9508ea 100644 --- a/src/GitProjectHealth-Model-Importer/GitModelImporter.class.st +++ b/src/GitProjectHealth-Model-Importer/GitModelImporter.class.st @@ -757,7 +757,7 @@ GitModelImporter >> parseDiffString: aDiff [ "parse diff string to create entities for each lines" | lines index firstChangeRange range | - aDiff diffString ifEmpty: [ ^ aDiff diffRanges ]. + (aDiff diffString isNil or: [ aDiff diffString isEmpty ]) ifTrue: [ ^ aDiff diffRanges ]. lines := aDiff diffString lines. index := 0. diff --git a/src/GitProjectHealth-Model-LocalImporter/GitLocalModelImporter.class.st b/src/GitProjectHealth-Model-LocalImporter/GitLocalModelImporter.class.st index df2dffa9..6f3f3bf6 100644 --- a/src/GitProjectHealth-Model-LocalImporter/GitLocalModelImporter.class.st +++ b/src/GitProjectHealth-Model-LocalImporter/GitLocalModelImporter.class.st @@ -39,7 +39,7 @@ GitLocalModelImporter >> createDiffFrom: patch and: delta [ glhDiff new_file: true ]. delta status = LGitDeltaTypeEnum git_delta_deleted ifTrue: [ glhDiff deleted_file: true ]. - glhDiff diffString: (patch patchString allButFirstLines: 2). + [glhDiff diffString: (patch patchString allButFirstLines: 2).] on: Error do: [ self flag: #'some illegal continuation byte for utf-8 encoding can appears here' ]. ^ glhDiff ] From 954456f8790bd334580c02cb79da2269f12d73fa Mon Sep 17 00:00:00 2001 From: Benoit Verhaeghe Date: Tue, 11 Aug 2026 10:20:03 +0200 Subject: [PATCH 18/75] assumption first author to modify a file is the creator of this file --- .../GLPUserDegreeOfAuthorshipAnalyzer.class.st | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/GitLabHealth-Model-Analysis/GLPUserDegreeOfAuthorshipAnalyzer.class.st b/src/GitLabHealth-Model-Analysis/GLPUserDegreeOfAuthorshipAnalyzer.class.st index 7136ca47..3b028738 100644 --- a/src/GitLabHealth-Model-Analysis/GLPUserDegreeOfAuthorshipAnalyzer.class.st +++ b/src/GitLabHealth-Model-Analysis/GLPUserDegreeOfAuthorshipAnalyzer.class.st @@ -40,8 +40,13 @@ Class { GLPUserDegreeOfAuthorshipAnalyzer >> authorCreatedFile [ "Paper: Should be 1 if yes. 0 if not" - "Us: we consider that files might be old, and so in this case 0" - ^ 0 + "Us: we consider that the author of the first commit on a file is the creator of the file" + + (self glhFile diffs sorted: [ :diff :b | + diff commit committed_date < b commit committed_date ]) first + commit commitCreator = self glhUser + ifTrue: [ ^ 1 ] + ifFalse: [ ^ 0 ] ] { #category : #accessing } From 76aeb07a44d127ed865e8bd132082b4812cc2774 Mon Sep 17 00:00:00 2001 From: Benoit Verhaeghe Date: Wed, 12 Aug 2026 08:22:12 +0200 Subject: [PATCH 19/75] fix add commits to repository at the end --- .../GitLocalModelImporter.class.st | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/GitProjectHealth-Model-LocalImporter/GitLocalModelImporter.class.st b/src/GitProjectHealth-Model-LocalImporter/GitLocalModelImporter.class.st index 6f3f3bf6..e2f10d15 100644 --- a/src/GitProjectHealth-Model-LocalImporter/GitLocalModelImporter.class.st +++ b/src/GitProjectHealth-Model-LocalImporter/GitLocalModelImporter.class.st @@ -236,8 +236,10 @@ GitLocalModelImporter >> importLatestCommitsOfProject: aGLHProject [ { #category : #'import - repositories' } GitLocalModelImporter >> importRepository: aGLHRepository [ + | commits | self importBranchesOfRepository: aGLHRepository. - self importCommitsOfRepository: aGLHRepository + commits := self importCommitsOfRepository: aGLHRepository. + aGLHRepository commits addAll: commits keyedBy: [ :el | el id ] ] { #category : #'import - commits' } From 8ab9d8c4eda335c0877585170f27f91064f1c3fa Mon Sep 17 00:00:00 2001 From: Benoit Verhaeghe Date: Wed, 12 Aug 2026 16:09:34 +0200 Subject: [PATCH 20/75] add a withDiffRanges for not loading all sometimes;.. --- .../GitModelImporter.class.st | 18 ++++++++++++++++-- .../GitLocalModelImporter.class.st | 7 ++++--- 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/src/GitProjectHealth-Model-Importer/GitModelImporter.class.st b/src/GitProjectHealth-Model-Importer/GitModelImporter.class.st index 2c9508ea..7e0e8478 100644 --- a/src/GitProjectHealth-Model-Importer/GitModelImporter.class.st +++ b/src/GitProjectHealth-Model-Importer/GitModelImporter.class.st @@ -15,7 +15,8 @@ Class { 'repoApi', 'withCommitDiffs', 'generalReader', - 'withCommitDiffsChanges' + 'withCommitDiffsChanges', + 'withDiffRanges' ], #classVars : [ 'currentImporter' @@ -730,7 +731,8 @@ GitModelImporter >> initReader [ GitModelImporter >> initialize [ super initialize. - self withFiles: false. + self withFiles: false. + self withDiffRanges: false. self withCommitsSince: (Date today - 1 week) asDateAndTime. userCatalogue := GLHUserCatalogueV2 new anImporter: self; @@ -861,6 +863,18 @@ GitModelImporter >> withCommitsSince: someDays [ ^ withCommitsSince ] +{ #category : #accessing } +GitModelImporter >> withDiffRanges [ + + ^ withDiffRanges +] + +{ #category : #accessing } +GitModelImporter >> withDiffRanges: anObject [ + + withDiffRanges := anObject +] + { #category : #accessing } GitModelImporter >> withFiles [ diff --git a/src/GitProjectHealth-Model-LocalImporter/GitLocalModelImporter.class.st b/src/GitProjectHealth-Model-LocalImporter/GitLocalModelImporter.class.st index e2f10d15..92b438fe 100644 --- a/src/GitProjectHealth-Model-LocalImporter/GitLocalModelImporter.class.st +++ b/src/GitProjectHealth-Model-LocalImporter/GitLocalModelImporter.class.st @@ -183,9 +183,10 @@ GitLocalModelImporter >> importDiffOfCommit: commit [ patch := gitDiff patchAt: idx. self createDiffFrom: patch and: delta ]) ]. "changes are added into the model during the import" - commit diffs do: [ :diff | - | ranges | - ranges := self importDiffRangesForDiff: diff ]. + self withDiffRanges ifTrue: [ + commit diffs do: [ :diff | + | ranges | + ranges := self importDiffRangesForDiff: diff ] ]. ^ commit diffs ] From e7b300b29d39c40dbb7e5d659b840542ab06be04 Mon Sep 17 00:00:00 2001 From: Benoit Verhaeghe Date: Thu, 13 Aug 2026 11:27:30 +0200 Subject: [PATCH 21/75] fix importUserByCommit --- .../GitLocalModelImporter.class.st | 30 ++++++++++--------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/src/GitProjectHealth-Model-LocalImporter/GitLocalModelImporter.class.st b/src/GitProjectHealth-Model-LocalImporter/GitLocalModelImporter.class.st index 92b438fe..e0a0303a 100644 --- a/src/GitProjectHealth-Model-LocalImporter/GitLocalModelImporter.class.st +++ b/src/GitProjectHealth-Model-LocalImporter/GitLocalModelImporter.class.st @@ -246,20 +246,22 @@ GitLocalModelImporter >> importRepository: aGLHRepository [ { #category : #'import - commits' } GitLocalModelImporter >> importUserByCommit: aGLHCommit [ - ^ self userCatalogue collectUsernames - at: aGLHCommit author_name - ifAbsent: [ - | glhUser | - glhUser := GLHUser new - id: aGLHCommit author_name; - username: aGLHCommit author_name; - name: aGLHCommit author_name; - email: aGLHCommit author_email; - yourself. - - self glhModel add: glhUser unless: self blockOnIdEquality. - self userCatalogue addUser: glhUser withName: glhUser username. - glhUser ] + | user | + user := self userCatalogue collectUsernames + at: aGLHCommit author_name + ifAbsentPut: [ + | glhUser | + glhUser := GLHUser new + id: aGLHCommit author_name; + username: aGLHCommit author_name; + name: aGLHCommit author_name; + email: aGLHCommit author_email; + yourself. + + glhUser ]. + self glhModel add: user unless: self blockOnIdEquality. + self userCatalogue addUser: user withName: user username. + ^ user ] { #category : #initialization } From 5ad0fe86826ae1a2e5d64958739f739c12e4f84b Mon Sep 17 00:00:00 2001 From: Benoit Verhaeghe Date: Tue, 18 Aug 2026 14:41:58 +0200 Subject: [PATCH 22/75] add tests for file import to avoid file importation duplication --- .../GLHFile.extension.st | 8 +++ .../GitLocalModelImporter.class.st | 49 +++++++++++++++---- 2 files changed, 47 insertions(+), 10 deletions(-) diff --git a/src/GitLabHealth-Model-Analysis/GLHFile.extension.st b/src/GitLabHealth-Model-Analysis/GLHFile.extension.st index 6c0a0fef..05d6df6f 100644 --- a/src/GitLabHealth-Model-Analysis/GLHFile.extension.st +++ b/src/GitLabHealth-Model-Analysis/GLHFile.extension.st @@ -16,3 +16,11 @@ GLHFile >> degreeOfAuthorshipAuthors [ glhFile: self; computeFileAuthors ] + +{ #category : #'*GitLabHealth-Model-Analysis' } +GLHFile >> mainAuthor [ + + ^ (GLPDegreeOfAuthorshipAnalyzer new + glhFile: self; + compute) +] diff --git a/src/GitProjectHealth-Model-LocalImporter/GitLocalModelImporter.class.st b/src/GitProjectHealth-Model-LocalImporter/GitLocalModelImporter.class.st index e0a0303a..3d35d2b2 100644 --- a/src/GitProjectHealth-Model-LocalImporter/GitLocalModelImporter.class.st +++ b/src/GitProjectHealth-Model-LocalImporter/GitLocalModelImporter.class.st @@ -14,16 +14,45 @@ Class { { #category : #'import - files' } GitLocalModelImporter >> buildGPHFileFor: aDiskDirectoryEntry [ - | file | - file := (aDiskDirectoryEntry isDirectory - ifTrue: [ GLHFileDirectory ] - ifFalse: [ GLHFileBlob ]) new. - self glhModel add: file. - file name: aDiskDirectoryEntry basename. - (file isKindOf: GLHFileDirectory) ifTrue: [ - file files: (aDiskDirectoryEntry children collect: [ :child | - self buildGPHFileFor: child ]) ]. - ^ file + | file root | + root := (self glhModel allWithSubTypesOf: GLHFile) + detect: [ :f | + f directoryOwner isNil and: [ f name = aDiskDirectoryEntry basename ] ] + ifFound: [ :f | f ] + ifNone: [ + file := (aDiskDirectoryEntry isDirectory + ifTrue: [ GLHFileDirectory ] + ifFalse: [ GLHFileBlob ]) new. + self glhModel add: file. + file name: aDiskDirectoryEntry basename ]. + (root isKindOf: GLHFileDirectory) ifTrue: [ + root files: (aDiskDirectoryEntry children collect: [ :child | + self buildGPHFileFor: child in: root ]) ]. + ^ root +] + +{ #category : #'import - files' } +GitLocalModelImporter >> buildGPHFileFor: aFileEntry in: parent [ + + | modelFile | + modelFile := parent files + detect: [ :childFile | + childFile name = aFileEntry basename ] + ifFound: [ :glhFile | glhFile ] + ifNone: [ + | file | + file := (aFileEntry isDirectory + ifTrue: [ GLHFileDirectory ] + ifFalse: [ GLHFileBlob ]) new. + self glhModel add: file. + file name: aFileEntry basename ]. + + (modelFile isKindOf: GLHFileDirectory) ifTrue: [ + modelFile files + addAll: (aFileEntry children collect: [ :child | + self buildGPHFileFor: child in: modelFile ]) + keyedBy: [ :futurChild | { futurChild name } ] ]. + ^ modelFile ] { #category : #'private - diffs' } From 700aae0fe0d45375d024a1b48bb6ca1b8b9ce82b Mon Sep 17 00:00:00 2001 From: Benoit Verhaeghe Date: Fri, 21 Aug 2026 08:45:30 +0200 Subject: [PATCH 23/75] replicate compute truck factor --- .../GLPTruckFactor.class.st | 109 ++++++++++++++++++ .../GLPTruckFactorInfo.class.st | 104 +++++++++++++++++ ...GLPUserDegreeOfAuthorshipAnalyzer.class.st | 2 +- .../GLHFileDirectory.class.st | 5 +- 4 files changed, 218 insertions(+), 2 deletions(-) create mode 100644 src/GitLabHealth-Model-Analysis/GLPTruckFactor.class.st create mode 100644 src/GitLabHealth-Model-Analysis/GLPTruckFactorInfo.class.st diff --git a/src/GitLabHealth-Model-Analysis/GLPTruckFactor.class.st b/src/GitLabHealth-Model-Analysis/GLPTruckFactor.class.st new file mode 100644 index 00000000..9ae6e4b6 --- /dev/null +++ b/src/GitLabHealth-Model-Analysis/GLPTruckFactor.class.st @@ -0,0 +1,109 @@ +" +I compute the truckfactor based on the files under directories +" +Class { + #name : #GLPTruckFactor, + #superclass : #Object, + #instVars : [ + 'directories', + 'tfInfo' + ], + #category : #'GitLabHealth-Model-Analysis-TruckFactor' +} + +{ #category : #'as yet unclassified' } +GLPTruckFactor >> blobFiles [ + + ^ self directories flatCollect: [ :directory | + directory allToScope: GLHFileBlob ] +] + +{ #category : #'as yet unclassified' } +GLPTruckFactor >> computeCoverageOf: repositoryFilesSize from: authorsMap [ + | authorsSet | + authorsSet := Set new. + + authorsMap valuesDo: [:files | + files do: [:file | + authorsSet add: file. + authorsSet size = repositoryFilesSize ifTrue: [ ^ 1.0 ] ] ]. + + ^ authorsSet size asFloat / repositoryFilesSize +] + +{ #category : #accessing } +GLPTruckFactor >> directories [ + + ^ directories +] + +{ #category : #accessing } +GLPTruckFactor >> directories: anObject [ + + directories := anObject +] + +{ #category : #accessing } +GLPTruckFactor >> filesAuthorMap [ + + | users authorsDictionary | + authorsDictionary := Dictionary new. + users := directories anyOne mooseModel allWithType: GLHUser. + self blobFiles + select: [ :fileBlob | fileBlob diffs isNotEmpty ] + thenCollect: [ :fileBlob | + fileBlob degreeOfAuthorshipAuthors associationsDo: [ :assoc | + (authorsDictionary at: assoc key ifAbsentPut: [ Set new ]) add: + fileBlob ] ]. + ^ authorsDictionary +] + +{ #category : #initialization } +GLPTruckFactor >> initialize [ + + super initialize. + tfInfo := GLPTruckFactorInfo new +] + +{ #category : #removing } +GLPTruckFactor >> removeTopAuthorOf: repFilesSize from: authorsMap [ + + | biggerNumber biggerDev | + biggerNumber := 0. + biggerDev := nil. + + authorsMap associationsDo: [ :assoc | + assoc value size > biggerNumber ifTrue: [ + biggerNumber := assoc value size. + biggerDev := assoc key ]. + + (biggerDev notNil and: [ + assoc value size = biggerNumber and: [ + (assoc key commits flatCollect: #diffs) size + > (biggerDev commits flatCollect: #diffs) size ] ]) ifTrue: [ + biggerDev := assoc key ] ]. + + tfInfo developers add: biggerDev. + authorsMap removeKey: biggerDev +] + +{ #category : #accessing } +GLPTruckFactor >> truckFactor [ + + | authorsMap numberOfFiles factor coverage | + authorsMap := self filesAuthorMap. + numberOfFiles := self blobFiles size. + factor := 0. + coverage := 1.0. + [ + authorsMap isNotEmpty and: [ + 0.5 < (self computeCoverageOf: numberOfFiles from: authorsMap) ] ] + whileTrue: [ + self removeTopAuthorOf: numberOfFiles from: authorsMap. + factor := factor + 1 ]. + tfInfo coverage: + (self computeCoverageOf: numberOfFiles from: authorsMap). + tfInfo truckFactor: factor. + tfInfo totalFiles: numberOfFiles. + ^ tfInfo +] diff --git a/src/GitLabHealth-Model-Analysis/GLPTruckFactorInfo.class.st b/src/GitLabHealth-Model-Analysis/GLPTruckFactorInfo.class.st new file mode 100644 index 00000000..2382540f --- /dev/null +++ b/src/GitLabHealth-Model-Analysis/GLPTruckFactorInfo.class.st @@ -0,0 +1,104 @@ +Class { + #name : #GLPTruckFactorInfo, + #superclass : #Object, + #instVars : [ + 'truckFactor', + 'totalFiles', + 'coverage', + 'developers' + ], + #category : #'GitLabHealth-Model-Analysis-TruckFactor' +} + +{ #category : #accessing } +GLPTruckFactorInfo >> coverage [ + + ^ coverage +] + +{ #category : #accessing } +GLPTruckFactorInfo >> coverage: anObject [ + + coverage := anObject +] + +{ #category : #accessing } +GLPTruckFactorInfo >> developers [ + + ^ developers +] + +{ #category : #accessing } +GLPTruckFactorInfo >> developers: anObject [ + + developers := anObject +] + +{ #category : #initialization } +GLPTruckFactorInfo >> initialize [ + + super initialize. + developers := OrderedCollection new +] + +{ #category : #initialization } +GLPTruckFactorInfo >> printOn: stream [ + + stream + nextPutAll: '*TF*'; + nextPut: $;; + print: truckFactor; + nextPut: $;; + print: totalFiles; + cr. + + coverage isNaN ifTrue: [ coverage := 0 ]. + + stream + nextPutAll: '*Coverage*'; + nextPut: $;; + print: (coverage * 100 roundTo: 0.01); + nextPut: $;; + print: (totalFiles * coverage) rounded; + cr. + + developers do: [ :developer | + | devFiles | + " devFiles := developer authorshipFiles size. +""stream + nextPutAll: repository; + nextPut: $;; + nextPutAll: developer name; + nextPut: $;; + print: ((devFiles asFloat / totalFiles) * 100) roundTo: 0.01; + nextPut: $;; + print: devFiles; + cr" + ]. + + stream +] + +{ #category : #accessing } +GLPTruckFactorInfo >> totalFiles [ + + ^ totalFiles +] + +{ #category : #accessing } +GLPTruckFactorInfo >> totalFiles: anObject [ + + totalFiles := anObject +] + +{ #category : #accessing } +GLPTruckFactorInfo >> truckFactor [ + + ^ truckFactor +] + +{ #category : #accessing } +GLPTruckFactorInfo >> truckFactor: anObject [ + + truckFactor := anObject +] diff --git a/src/GitLabHealth-Model-Analysis/GLPUserDegreeOfAuthorshipAnalyzer.class.st b/src/GitLabHealth-Model-Analysis/GLPUserDegreeOfAuthorshipAnalyzer.class.st index 3b028738..0b0a4a57 100644 --- a/src/GitLabHealth-Model-Analysis/GLPUserDegreeOfAuthorshipAnalyzer.class.st +++ b/src/GitLabHealth-Model-Analysis/GLPUserDegreeOfAuthorshipAnalyzer.class.st @@ -41,7 +41,7 @@ GLPUserDegreeOfAuthorshipAnalyzer >> authorCreatedFile [ "Paper: Should be 1 if yes. 0 if not" "Us: we consider that the author of the first commit on a file is the creator of the file" - + (self glhFile diffs sorted: [ :diff :b | diff commit committed_date < b commit committed_date ]) first commit commitCreator = self glhUser diff --git a/src/GitLabHealth-Model/GLHFileDirectory.class.st b/src/GitLabHealth-Model/GLHFileDirectory.class.st index 9838cb5b..bc8ffa29 100644 --- a/src/GitLabHealth-Model/GLHFileDirectory.class.st +++ b/src/GitLabHealth-Model/GLHFileDirectory.class.st @@ -16,7 +16,10 @@ Class { #name : #GLHFileDirectory, #superclass : #GLHFile, #instVars : [ - '#files => FMMany type: #GLHFile opposite: #directoryOwner' + '#files => FMMany type: #GLHFile opposite: #directoryOwner', + '#coverage', + '#numberOfFilesTop1Dev', + '#truckFactor' ], #category : #'GitLabHealth-Model-Entities' } From 299f95d9f3af6edc6e00bcd644c9556bb6f76f4b Mon Sep 17 00:00:00 2001 From: Benoit Verhaeghe Date: Fri, 21 Aug 2026 09:00:58 +0200 Subject: [PATCH 24/75] add additionnal tests in baseline --- .../BaselineOfGitLabHealth.class.st | 8 ++- .../GitLocalModelImporterTest.class.st | 60 +++++++++++++++++++ .../package.st | 1 + 3 files changed, 68 insertions(+), 1 deletion(-) create mode 100644 src/GitProjectHealth-Model-LocalImporter-Tests/GitLocalModelImporterTest.class.st create mode 100644 src/GitProjectHealth-Model-LocalImporter-Tests/package.st diff --git a/src/BaselineOfGitLabHealth/BaselineOfGitLabHealth.class.st b/src/BaselineOfGitLabHealth/BaselineOfGitLabHealth.class.st index c3dc100e..1d4ccd7f 100644 --- a/src/BaselineOfGitLabHealth/BaselineOfGitLabHealth.class.st +++ b/src/BaselineOfGitLabHealth/BaselineOfGitLabHealth.class.st @@ -103,7 +103,8 @@ BaselineOfGitLabHealth >> defineGroups: spec [ 'GitProjectHealth-Model-Importer-Tests' 'BitBucketHealth-Model-Importer' 'BitBucketHealth-Model-Importer-Tests' - 'GitProjectHealth-Model-LocalImporter' ). + 'GitProjectHealth-Model-LocalImporter' + 'GitProjectHealth-Model-LocalImporter-Tests' ). spec group: 'default' with: #( 'Core' ) ] @@ -185,6 +186,11 @@ BaselineOfGitLabHealth >> definePackages: spec [ spec requires: #( 'GitProjectHealth-Model-Importer' 'GitLabHealth-Model-Extension' ) ]. + + + spec package: 'GitProjectHealth-Model-LocalImporter-Tests' with: [ + spec requires: + #( 'GitProjectHealth-Model-LocalImporter' ) ]. "model extension" spec diff --git a/src/GitProjectHealth-Model-LocalImporter-Tests/GitLocalModelImporterTest.class.st b/src/GitProjectHealth-Model-LocalImporter-Tests/GitLocalModelImporterTest.class.st new file mode 100644 index 00000000..8aa070bb --- /dev/null +++ b/src/GitProjectHealth-Model-LocalImporter-Tests/GitLocalModelImporterTest.class.st @@ -0,0 +1,60 @@ +" +A GitLocalModelImporterTest is a test class for testing the behavior of GitLocalModelImporter +" +Class { + #name : #GitLocalModelImporterTest, + #superclass : #TestCase, + #instVars : [ + 'localImporter', + 'model', + 'root' + ], + #category : #'GitProjectHealth-Model-LocalImporter-Tests' +} + +{ #category : #running } +GitLocalModelImporterTest >> setUp [ + + super setUp. + localImporter := GitLocalModelImporter new. + model := GLHModel new. + localImporter glhModel: model. + root := FileSystem memory root. + (root / 'src') ensureCreateDirectory. + (root / 'src' / 'en') ensureCreateDirectory. + (root / 'src' / 'en' / 'test.st') ensureCreateFile +] + +{ #category : #tests } +GitLocalModelImporterTest >> testBuildGPHFileFor [ + + localImporter buildGPHFileFor: root. + self assert: (model allWithSubTypesOf: GLHFile) isNotEmpty +] + +{ #category : #tests } +GitLocalModelImporterTest >> testBuildGPHFileForMultipleTimes [ + "in case there are more and more files, we can parse several times. But we need to have only one time each file" + + localImporter buildGPHFileFor: root. + localImporter buildGPHFileFor: root. + self + assert: ((model allWithType: GLHFileDirectory) select: [ :f | + f path endsWith: '/src' ]) size + equals: 1 +] + +{ #category : #tests } +GitLocalModelImporterTest >> testBuildGPHFileForMultipleTimesAddItems [ + "in case there are more and more files, we can parse several times. But we need to have only one time each file" + + | size sizeAfter | + localImporter buildGPHFileFor: root. + size := (model allWithSubTypesOf: GLHFile) size. + (root / 'src' / 'fr') ensureCreateDirectory. + (root / 'src' / 'fr' / 'test.st') ensureCreateFile. + localImporter buildGPHFileFor: root. + sizeAfter := (model allWithSubTypesOf: GLHFile) size. + + self assert: size + 2 equals: sizeAfter +] diff --git a/src/GitProjectHealth-Model-LocalImporter-Tests/package.st b/src/GitProjectHealth-Model-LocalImporter-Tests/package.st new file mode 100644 index 00000000..10572b1d --- /dev/null +++ b/src/GitProjectHealth-Model-LocalImporter-Tests/package.st @@ -0,0 +1 @@ +Package { #name : #'GitProjectHealth-Model-LocalImporter-Tests' } From 86a55017e368dff1e8a4b9464c1bb0253b4d6a44 Mon Sep 17 00:00:00 2001 From: Benoit Verhaeghe Date: Mon, 24 Aug 2026 14:25:06 +0200 Subject: [PATCH 25/75] fix order diff is made between local commit to not invert add and removal --- .../GitLocalModelImporter.class.st | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/GitProjectHealth-Model-LocalImporter/GitLocalModelImporter.class.st b/src/GitProjectHealth-Model-LocalImporter/GitLocalModelImporter.class.st index 3d35d2b2..97b278e3 100644 --- a/src/GitProjectHealth-Model-LocalImporter/GitLocalModelImporter.class.st +++ b/src/GitProjectHealth-Model-LocalImporter/GitLocalModelImporter.class.st @@ -6,7 +6,8 @@ Class { #superclass : #GitModelImporter, #instVars : [ 'localGitRepositoryFileReference', - 'gitRepository' + 'gitRepository', + 'indexedFiles' ], #category : #'GitProjectHealth-Model-LocalImporter' } @@ -195,17 +196,17 @@ GitLocalModelImporter >> importCommitsOfRepository: aGPHRepository since: sinceD { #category : #'import - diffs' } GitLocalModelImporter >> importDiffOfCommit: commit [ - | sourceCommit targetCommit gitDiff | + | sourceCommit parentCommit gitDiff | commit parentCommits ifNotEmpty: [ sourceCommit := commit cacheAt: #importerOrigin ifAbsent: [ Error signal: 'missing importer origin' ]. - targetCommit := commit parentCommits anyOne + parentCommit := commit parentCommits anyOne cacheAt: #importerOrigin ifAbsent: [ Error signal: 'missing importer origin' ]. - gitDiff := sourceCommit tree diffTo: targetCommit tree. + gitDiff := parentCommit tree diffTo: sourceCommit tree. commit diffs: ((1 to: gitDiff numberOfDeltas) collect: [ :idx | | delta patch | delta := gitDiff deltas at: idx. From 08b1e863a45ab6ba13ef3aa78a7a51c993f079bc Mon Sep 17 00:00:00 2001 From: Benoit Verhaeghe Date: Mon, 24 Aug 2026 14:54:42 +0200 Subject: [PATCH 26/75] can retrieve renamed file in local git import --- .../GitLocalModelImporter.class.st | 30 ++++++++++++------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/src/GitProjectHealth-Model-LocalImporter/GitLocalModelImporter.class.st b/src/GitProjectHealth-Model-LocalImporter/GitLocalModelImporter.class.st index 97b278e3..d3debe8e 100644 --- a/src/GitProjectHealth-Model-LocalImporter/GitLocalModelImporter.class.st +++ b/src/GitProjectHealth-Model-LocalImporter/GitLocalModelImporter.class.st @@ -56,6 +56,24 @@ GitLocalModelImporter >> buildGPHFileFor: aFileEntry in: parent [ ^ modelFile ] +{ #category : #'import - commits' } +GitLocalModelImporter >> connectDiffs: diffs inside: aGPHRepository [ + "could be refactor for a classic lazy accessor. But maybe this project can also be use for several repository. So to think about later" + + indexedFiles ifNil: [ + indexedFiles := (aGPHRepository allToScope: GLHFile) + collect: [ :el | el path -> el ] + as: Dictionary ]. + diffs do: [ :diff | + indexedFiles + at: diff new_path + ifPresent: [ :gphFile | + diff addFile: gphFile. + "support for file renaming" + indexedFiles at: diff old_path put: gphFile ] + ifAbsent: [ "not supported yet" ] ] +] + { #category : #'private - diffs' } GitLocalModelImporter >> createDiffFrom: patch and: delta [ @@ -179,16 +197,7 @@ GitLocalModelImporter >> importCommitsOfRepository: aGPHRepository since: sinceD diffs := commits flatCollect: [ :commit | self importDiffOfCommit: commit ]. self withFiles ifTrue: [ - | indexedFiles | - indexedFiles := (aGPHRepository allToScope: GLHFile) - collect: [ :el | el path -> el ] - as: Dictionary. - - diffs do: [ :diff | - indexedFiles - at: diff new_path - ifPresent: [ :gphFile | diff addFile: gphFile ] - ifAbsent: [ "not supported yet" ] ] ] ]. + self connectDiffs: diffs inside: aGPHRepository ] ]. ^ commits ] @@ -207,6 +216,7 @@ GitLocalModelImporter >> importDiffOfCommit: commit [ ifAbsent: [ Error signal: 'missing importer origin' ]. gitDiff := parentCommit tree diffTo: sourceCommit tree. + gitDiff diffFindSimilar. commit diffs: ((1 to: gitDiff numberOfDeltas) collect: [ :idx | | delta patch | delta := gitDiff deltas at: idx. From 3d7ceeff615eb2bc7970c689eabcbdd09e946917 Mon Sep 17 00:00:00 2001 From: Benoit Verhaeghe Date: Tue, 25 Aug 2026 14:52:46 +0200 Subject: [PATCH 27/75] add git bridge for test support --- .../BaselineOfGitLabHealth.class.st | 32 ++++++++++++------- .../GPHBridge.class.st | 17 ++++++++++ .../ManifestGitProjectHealthBridge.class.st | 8 +++++ src/GitProjectHealth-Bridge/package.st | 1 + 4 files changed, 46 insertions(+), 12 deletions(-) create mode 100644 src/GitProjectHealth-Bridge/GPHBridge.class.st create mode 100644 src/GitProjectHealth-Bridge/ManifestGitProjectHealthBridge.class.st create mode 100644 src/GitProjectHealth-Bridge/package.st diff --git a/src/BaselineOfGitLabHealth/BaselineOfGitLabHealth.class.st b/src/BaselineOfGitLabHealth/BaselineOfGitLabHealth.class.st index 1d4ccd7f..33f491b4 100644 --- a/src/BaselineOfGitLabHealth/BaselineOfGitLabHealth.class.st +++ b/src/BaselineOfGitLabHealth/BaselineOfGitLabHealth.class.st @@ -49,18 +49,19 @@ BaselineOfGitLabHealth >> defineDependencies: spec [ spec baseline: 'GitlabAPI' - with: [ - spec repository: 'github://Evref-BL/Gitlab-Pharo-API:main' ]. - - spec + with: [ spec repository: 'github://Evref-BL/Gitlab-Pharo-API:main' ]. + + spec baseline: 'GithubAPI' - with: [ - spec repository: 'github://Evref-BL/github-Pharo-API:main' ]. + with: [ spec repository: 'github://Evref-BL/github-Pharo-API:main' ]. spec baseline: 'BitbucketPharoAPI' with: [ - spec repository: 'github://Evref-BL/Bitbucket-Pharo-API:main' ] + spec repository: 'github://Evref-BL/Bitbucket-Pharo-API:main' ]. + spec + baseline: 'GitBridge' + with: [ spec repository: 'github://jecisc/GitBridge:v1.0.2/src' ] ] { #category : #baselines } @@ -140,6 +141,13 @@ BaselineOfGitLabHealth >> definePackages: spec [ package: 'GitProjectHealth-Model-Importer-Tests' with: [ spec requires: #( 'GitProjectHealth-Model-Importer' ) ]. + "test utility" + spec + package: 'GitProjectHealth-Bridge' + with: [ spec requires: #( 'GitBridge' ) ]. + + + "model" spec package: 'GitLabHealth-Model'; @@ -186,11 +194,11 @@ BaselineOfGitLabHealth >> definePackages: spec [ spec requires: #( 'GitProjectHealth-Model-Importer' 'GitLabHealth-Model-Extension' ) ]. - - - spec package: 'GitProjectHealth-Model-LocalImporter-Tests' with: [ - spec requires: - #( 'GitProjectHealth-Model-LocalImporter' ) ]. + + + spec + package: 'GitProjectHealth-Model-LocalImporter-Tests' + with: [ spec requires: #( 'GitProjectHealth-Model-LocalImporter' ) ]. "model extension" spec diff --git a/src/GitProjectHealth-Bridge/GPHBridge.class.st b/src/GitProjectHealth-Bridge/GPHBridge.class.st new file mode 100644 index 00000000..9314ef6d --- /dev/null +++ b/src/GitProjectHealth-Bridge/GPHBridge.class.st @@ -0,0 +1,17 @@ +Class { + #name : #GPHBridge, + #superclass : #GitBridge, + #category : #'GitProjectHealth-Bridge' +} + +{ #category : #'class initialization' } +GPHBridge class >> initialize [ + + SessionManager default registerSystemClassNamed: self name +] + +{ #category : #'class initialization' } +GPHBridge class >> testRessource [ + + ^ self root / 'res' +] diff --git a/src/GitProjectHealth-Bridge/ManifestGitProjectHealthBridge.class.st b/src/GitProjectHealth-Bridge/ManifestGitProjectHealthBridge.class.st new file mode 100644 index 00000000..9e51877c --- /dev/null +++ b/src/GitProjectHealth-Bridge/ManifestGitProjectHealthBridge.class.st @@ -0,0 +1,8 @@ +" +This is a the bridge using [GitBridge](https://github.com/jecisc/gitbridge) that link this repository in Pharo to the git source in local +" +Class { + #name : #ManifestGitProjectHealthBridge, + #superclass : #PackageManifest, + #category : #'GitProjectHealth-Bridge-Manifest' +} diff --git a/src/GitProjectHealth-Bridge/package.st b/src/GitProjectHealth-Bridge/package.st new file mode 100644 index 00000000..438cb39b --- /dev/null +++ b/src/GitProjectHealth-Bridge/package.st @@ -0,0 +1 @@ +Package { #name : #'GitProjectHealth-Bridge' } From 42fefc88bd321fea4245d7e04494ecc3b7e67b21 Mon Sep 17 00:00:00 2001 From: Benoit Verhaeghe Date: Wed, 26 Aug 2026 11:51:50 +0200 Subject: [PATCH 28/75] introducing test resources to test with reald git folder --- .../GPHBridge.class.st | 15 ++++++- .../GitModelImporterTest.class.st | 2 +- ...ocalModelImporterWithTestResource.class.st | 20 +++++++++ ...ModelImporterWithTestResourceTest.class.st | 45 +++++++++++++++++++ 4 files changed, 79 insertions(+), 3 deletions(-) create mode 100644 src/GitProjectHealth-Model-LocalImporter-Tests/GitLocalModelImporterWithTestResource.class.st create mode 100644 src/GitProjectHealth-Model-LocalImporter-Tests/GitLocalModelImporterWithTestResourceTest.class.st diff --git a/src/GitProjectHealth-Bridge/GPHBridge.class.st b/src/GitProjectHealth-Bridge/GPHBridge.class.st index 9314ef6d..3c30cdbc 100644 --- a/src/GitProjectHealth-Bridge/GPHBridge.class.st +++ b/src/GitProjectHealth-Bridge/GPHBridge.class.st @@ -11,7 +11,18 @@ GPHBridge class >> initialize [ ] { #category : #'class initialization' } -GPHBridge class >> testRessource [ - +GPHBridge class >> resources [ ^ self root / 'res' ] + +{ #category : #'class initialization' } +GPHBridge class >> testResources [ + + ^ self resources / 'testResource' +] + +{ #category : #'class initialization' } +GPHBridge class >> testResourcesZip [ + + ^ self resources / 'testResource.zip' +] diff --git a/src/GitProjectHealth-Model-Importer-Tests/GitModelImporterTest.class.st b/src/GitProjectHealth-Model-Importer-Tests/GitModelImporterTest.class.st index 9d5426ef..98bdd9cd 100644 --- a/src/GitProjectHealth-Model-Importer-Tests/GitModelImporterTest.class.st +++ b/src/GitProjectHealth-Model-Importer-Tests/GitModelImporterTest.class.st @@ -11,7 +11,7 @@ Class { #category : #'GitProjectHealth-Model-Importer-Tests' } -{ #category : #tests } +{ #category : #running } GitModelImporterTest >> setUp [ super setUp. diff --git a/src/GitProjectHealth-Model-LocalImporter-Tests/GitLocalModelImporterWithTestResource.class.st b/src/GitProjectHealth-Model-LocalImporter-Tests/GitLocalModelImporterWithTestResource.class.st new file mode 100644 index 00000000..0abc3b0d --- /dev/null +++ b/src/GitProjectHealth-Model-LocalImporter-Tests/GitLocalModelImporterWithTestResource.class.st @@ -0,0 +1,20 @@ +Class { + #name : #GitLocalModelImporterWithTestResource, + #superclass : #TestResource, + #category : #'GitProjectHealth-Model-LocalImporter-Tests' +} + +{ #category : #running } +GitLocalModelImporterWithTestResource >> setUp [ + + super setUp. + ZipArchive extractFrom: (GPHBridge testResourcesZip) to: GPHBridge testResources + +] + +{ #category : #running } +GitLocalModelImporterWithTestResource >> tearDown [ + + GPHBridge testResources ensureDeleteAll. + super tearDown +] diff --git a/src/GitProjectHealth-Model-LocalImporter-Tests/GitLocalModelImporterWithTestResourceTest.class.st b/src/GitProjectHealth-Model-LocalImporter-Tests/GitLocalModelImporterWithTestResourceTest.class.st new file mode 100644 index 00000000..9604eae8 --- /dev/null +++ b/src/GitProjectHealth-Model-LocalImporter-Tests/GitLocalModelImporterWithTestResourceTest.class.st @@ -0,0 +1,45 @@ +" +This tests uses the test repository under GPHBridge (the files of the repository) +" +Class { + #name : #GitLocalModelImporterWithTestResourceTest, + #superclass : #TestCase, + #instVars : [ + 'localImporter', + 'model', + 'repository' + ], + #category : #'GitProjectHealth-Model-LocalImporter-Tests' +} + +{ #category : #accessing } +GitLocalModelImporterWithTestResourceTest class >> resources [ + + ^ Array with: GitLocalModelImporterWithTestResource +] + +{ #category : #running } +GitLocalModelImporterWithTestResourceTest >> setUp [ + + super setUp. + localImporter := GitLocalModelImporter new. + model := GLHModel new. + localImporter glhModel: model. + localImporter withFiles: true. + localImporter withCommitDiffs: true. + localImporter withDiffRanges: true. + localImporter withCommitsSince: (DateAndTime year: 2026 day: 1). + + repository := GLHRepository new + cacheAt: #localImporterReference + put: GPHBridge testResources asFileReference; + yourself. + + model add: repository. + localImporter importRepository: repository +] + +{ #category : #tests } +GitLocalModelImporterWithTestResourceTest >> testHasRenamedField [ + self halt. +] From c4d6d48800a62dbef40ee0fe8f855588938021c9 Mon Sep 17 00:00:00 2001 From: Benoit Verhaeghe Date: Wed, 26 Aug 2026 14:06:39 +0200 Subject: [PATCH 29/75] add two tests one for first commit diff second for detecting rename --- ...ModelImporterWithTestResourceTest.class.st | 18 ++++++++- .../GitLocalModelImporter.class.st | 40 +++++++++++-------- 2 files changed, 40 insertions(+), 18 deletions(-) diff --git a/src/GitProjectHealth-Model-LocalImporter-Tests/GitLocalModelImporterWithTestResourceTest.class.st b/src/GitProjectHealth-Model-LocalImporter-Tests/GitLocalModelImporterWithTestResourceTest.class.st index 9604eae8..9e9ee24e 100644 --- a/src/GitProjectHealth-Model-LocalImporter-Tests/GitLocalModelImporterWithTestResourceTest.class.st +++ b/src/GitProjectHealth-Model-LocalImporter-Tests/GitLocalModelImporterWithTestResourceTest.class.st @@ -32,14 +32,28 @@ GitLocalModelImporterWithTestResourceTest >> setUp [ repository := GLHRepository new cacheAt: #localImporterReference - put: GPHBridge testResources asFileReference; + put: (GPHBridge testResources / 'testResource') asFileReference; yourself. model add: repository. localImporter importRepository: repository ] +{ #category : #tests } +GitLocalModelImporterWithTestResourceTest >> testFirstCommitHasADiff [ + + | commit | + commit := (model allWithType: GLHCommit) detect: [ :commi | + commi parentCommits isEmpty ]. + self assert: commit diffs isNotEmpty +] + { #category : #tests } GitLocalModelImporterWithTestResourceTest >> testHasRenamedField [ - self halt. + + | file | + file := (model allWithType: GLHFileBlob) detect: [ :fileBlob | + fileBlob path endsWith: 'renamed-hello.st' ]. + self assert: file diffs size equals: 3. + self assert: file diffs second renamed_file ] diff --git a/src/GitProjectHealth-Model-LocalImporter/GitLocalModelImporter.class.st b/src/GitProjectHealth-Model-LocalImporter/GitLocalModelImporter.class.st index d3debe8e..294cead4 100644 --- a/src/GitProjectHealth-Model-LocalImporter/GitLocalModelImporter.class.st +++ b/src/GitProjectHealth-Model-LocalImporter/GitLocalModelImporter.class.st @@ -64,6 +64,7 @@ GitLocalModelImporter >> connectDiffs: diffs inside: aGPHRepository [ indexedFiles := (aGPHRepository allToScope: GLHFile) collect: [ :el | el path -> el ] as: Dictionary ]. + self halt. diffs do: [ :diff | indexedFiles at: diff new_path @@ -206,22 +207,29 @@ GitLocalModelImporter >> importCommitsOfRepository: aGPHRepository since: sinceD GitLocalModelImporter >> importDiffOfCommit: commit [ | sourceCommit parentCommit gitDiff | - commit parentCommits ifNotEmpty: [ - sourceCommit := commit - cacheAt: #importerOrigin - ifAbsent: [ - Error signal: 'missing importer origin' ]. - parentCommit := commit parentCommits anyOne - cacheAt: #importerOrigin - ifAbsent: [ - Error signal: 'missing importer origin' ]. - gitDiff := parentCommit tree diffTo: sourceCommit tree. - gitDiff diffFindSimilar. - commit diffs: ((1 to: gitDiff numberOfDeltas) collect: [ :idx | - | delta patch | - delta := gitDiff deltas at: idx. - patch := gitDiff patchAt: idx. - self createDiffFrom: patch and: delta ]) ]. + sourceCommit := commit + cacheAt: #importerOrigin + ifAbsent: [ Error signal: 'missing importer origin' ]. + gitDiff := LGitDiff of: sourceCommit repository. + + commit parentCommits + ifNotEmpty: [ + parentCommit := commit parentCommits anyOne + cacheAt: #importerOrigin + ifAbsent: [ + Error signal: 'missing importer origin' ]. + gitDiff := gitDiff + diffTree: parentCommit tree + toTree: sourceCommit tree ] + ifEmpty: [ + gitDiff := gitDiff diffTree: FFIExternalObject null toTree: sourceCommit tree ]. + + gitDiff diffFindSimilar. + commit diffs: ((1 to: gitDiff numberOfDeltas) collect: [ :idx | + | delta patch | + delta := gitDiff deltas at: idx. + patch := gitDiff patchAt: idx. + self createDiffFrom: patch and: delta ]). "changes are added into the model during the import" self withDiffRanges ifTrue: [ commit diffs do: [ :diff | From 55b0994c667da10207720dfe6a05e852a73532b0 Mon Sep 17 00:00:00 2001 From: Benoit Verhaeghe Date: Wed, 26 Aug 2026 14:20:57 +0200 Subject: [PATCH 30/75] clean code fix dependencies --- src/BaselineOfGitLabHealth/BaselineOfGitLabHealth.class.st | 2 +- .../GitLocalModelImporter.class.st | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/BaselineOfGitLabHealth/BaselineOfGitLabHealth.class.st b/src/BaselineOfGitLabHealth/BaselineOfGitLabHealth.class.st index 33f491b4..26d51978 100644 --- a/src/BaselineOfGitLabHealth/BaselineOfGitLabHealth.class.st +++ b/src/BaselineOfGitLabHealth/BaselineOfGitLabHealth.class.st @@ -198,7 +198,7 @@ BaselineOfGitLabHealth >> definePackages: spec [ spec package: 'GitProjectHealth-Model-LocalImporter-Tests' - with: [ spec requires: #( 'GitProjectHealth-Model-LocalImporter' ) ]. + with: [ spec requires: #( 'GitProjectHealth-Model-LocalImporter' 'GitProjectHealth-Bridge' ) ]. "model extension" spec diff --git a/src/GitProjectHealth-Model-LocalImporter/GitLocalModelImporter.class.st b/src/GitProjectHealth-Model-LocalImporter/GitLocalModelImporter.class.st index 294cead4..f3f7bb06 100644 --- a/src/GitProjectHealth-Model-LocalImporter/GitLocalModelImporter.class.st +++ b/src/GitProjectHealth-Model-LocalImporter/GitLocalModelImporter.class.st @@ -64,7 +64,6 @@ GitLocalModelImporter >> connectDiffs: diffs inside: aGPHRepository [ indexedFiles := (aGPHRepository allToScope: GLHFile) collect: [ :el | el path -> el ] as: Dictionary ]. - self halt. diffs do: [ :diff | indexedFiles at: diff new_path From eb0afc40748f306aa2e47e2486641ca40e8b6030 Mon Sep 17 00:00:00 2001 From: Benoit Verhaeghe Date: Wed, 26 Aug 2026 14:25:24 +0200 Subject: [PATCH 31/75] pushing test resource --- res/testResource.zip | Bin 0 -> 43126 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 res/testResource.zip diff --git a/res/testResource.zip b/res/testResource.zip new file mode 100644 index 0000000000000000000000000000000000000000..fe92dba9075480db76f3dd2fbb0fac707f4c3b27 GIT binary patch literal 43126 zcmd^oby$_z_CF~|35wF)-CcroN=ir^y1Nu90YO?yrKA<o|MK7e!Gt4+ zb2W2uRWoyOaC0^>V{`LFhl5AjhKGau@&C}&z=T6dE0453ey;y$3Kb6Z4^80#n=&+1 z62GRRuFb}3c(PS!jn)DiH!-?wVqs)#w4tP^wQ)f!$I|M807QP>P;cocuKu}Zxxv7+vVOe(RZ>b^eA*ID4Q9X8b7+cFiT8s-2)GI zT>%AEj4gOw3&=GS`26hp#Ld$xkbtY;Y-SIzGc#qeG_$pJV0CeYM1T$0aZTU@`pr{q zm+k53xbf`6%w6_;+sol!tA4=e65sm-5*`mKJpD=KPU;4f@ppRicS{K*1vd>Qj)6Sw!YL(V1WzkYthlG9<2=)@%;@ITjm+;exPJ?mN7#iE!!6bNhNca- z%A>OFwfaft=ggKhRNQtHPw~c3;$L6OK)H{Z`A; z8Y5=sHt(7MO?m&6lY$eWI@=H+3$q&jn$zEH4@mw=2+(SMZz{r*%2ywP7O18N_VGRMvT-97_p z%>7~c@ng<3HFGg>wsLf}amuG2b>t*g>dtR<3tJJ})O5f4W ztR>skEvI>1MUF+8wUe2?qq|T227Sj3xFxvGr<7KM{~e-yVJKu`K�CpPzDrJ0g^o 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

%bFM^7)i2>Eg@vq*4tQ-%ryv(DejHLm@=Q2BT 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 Date: Wed, 26 Aug 2026 16:43:47 +0200 Subject: [PATCH 32/75] use git entries instead of filesystem to avoid importing useless files --- ...ModelImporterWithTestResourceTest.class.st | 26 ++++++++++++++- .../GitLocalModelImporter.class.st | 32 +++++++++---------- 2 files changed, 41 insertions(+), 17 deletions(-) diff --git a/src/GitProjectHealth-Model-LocalImporter-Tests/GitLocalModelImporterWithTestResourceTest.class.st b/src/GitProjectHealth-Model-LocalImporter-Tests/GitLocalModelImporterWithTestResourceTest.class.st index 9e9ee24e..b38a8372 100644 --- a/src/GitProjectHealth-Model-LocalImporter-Tests/GitLocalModelImporterWithTestResourceTest.class.st +++ b/src/GitProjectHealth-Model-LocalImporter-Tests/GitLocalModelImporterWithTestResourceTest.class.st @@ -36,13 +36,13 @@ GitLocalModelImporterWithTestResourceTest >> setUp [ yourself. model add: repository. - localImporter importRepository: repository ] { #category : #tests } GitLocalModelImporterWithTestResourceTest >> testFirstCommitHasADiff [ | commit | + localImporter importRepository: repository. commit := (model allWithType: GLHCommit) detect: [ :commi | commi parentCommits isEmpty ]. self assert: commit diffs isNotEmpty @@ -52,8 +52,32 @@ GitLocalModelImporterWithTestResourceTest >> testFirstCommitHasADiff [ GitLocalModelImporterWithTestResourceTest >> testHasRenamedField [ | file | + localImporter importRepository: repository. file := (model allWithType: GLHFileBlob) detect: [ :fileBlob | fileBlob path endsWith: 'renamed-hello.st' ]. self assert: file diffs size equals: 3. self assert: file diffs second renamed_file ] + +{ #category : #tests } +GitLocalModelImporterWithTestResourceTest >> testImportFilesOfBranch [ + + | file | + localImporter importRepository: repository. + file := (model allWithType: GLHFileBlob) detect: [ :fi | + fi name = 'renamed-hello.st' ]. + self assert: file isNotNil +] + +{ #category : #tests } +GitLocalModelImporterWithTestResourceTest >> testImportFilesOfBranchDoNotImportNotCommitedFile [ + + | file | + (GPHBridge testResources / 'testResource' / 'wrong-file.st') + ensureCreateFile. + localImporter importRepository: repository. + file := (model allWithType: GLHFileBlob) + detect: [ :fi | fi name = 'wrong-file.st' ] + ifNone: [ nil ]. + self assert: file isNil +] diff --git a/src/GitProjectHealth-Model-LocalImporter/GitLocalModelImporter.class.st b/src/GitProjectHealth-Model-LocalImporter/GitLocalModelImporter.class.st index f3f7bb06..4295bd16 100644 --- a/src/GitProjectHealth-Model-LocalImporter/GitLocalModelImporter.class.st +++ b/src/GitProjectHealth-Model-LocalImporter/GitLocalModelImporter.class.st @@ -18,16 +18,16 @@ GitLocalModelImporter >> buildGPHFileFor: aDiskDirectoryEntry [ | file root | root := (self glhModel allWithSubTypesOf: GLHFile) detect: [ :f | - f directoryOwner isNil and: [ f name = aDiskDirectoryEntry basename ] ] + f directoryOwner isNil and: [ f name = aDiskDirectoryEntry filename ] ] ifFound: [ :f | f ] ifNone: [ - file := (aDiskDirectoryEntry isDirectory - ifTrue: [ GLHFileDirectory ] - ifFalse: [ GLHFileBlob ]) new. + file := (aDiskDirectoryEntry isBlob + ifFalse: [ GLHFileDirectory ] + ifTrue: [ GLHFileBlob ]) new. self glhModel add: file. - file name: aDiskDirectoryEntry basename ]. + file name: aDiskDirectoryEntry filename ]. (root isKindOf: GLHFileDirectory) ifTrue: [ - root files: (aDiskDirectoryEntry children collect: [ :child | + root files: (aDiskDirectoryEntry entries collect: [ :child | self buildGPHFileFor: child in: root ]) ]. ^ root ] @@ -38,19 +38,19 @@ GitLocalModelImporter >> buildGPHFileFor: aFileEntry in: parent [ | modelFile | modelFile := parent files detect: [ :childFile | - childFile name = aFileEntry basename ] + childFile name = aFileEntry filename ] ifFound: [ :glhFile | glhFile ] ifNone: [ | file | - file := (aFileEntry isDirectory - ifTrue: [ GLHFileDirectory ] - ifFalse: [ GLHFileBlob ]) new. + file := (aFileEntry isBlob + ifFalse: [ GLHFileDirectory ] + ifTrue: [ GLHFileBlob ]) new. self glhModel add: file. - file name: aFileEntry basename ]. + file name: aFileEntry filename ]. (modelFile isKindOf: GLHFileDirectory) ifTrue: [ modelFile files - addAll: (aFileEntry children collect: [ :child | + addAll: (aFileEntry entries collect: [ :child | self buildGPHFileFor: child in: modelFile ]) keyedBy: [ :futurChild | { futurChild name } ] ]. ^ modelFile @@ -245,11 +245,11 @@ GitLocalModelImporter >> importFilesOfBranch: aGLHBranch [ gitRepository := self gitRepositoryOf: aGLHBranch repository. gitBranch := gitRepository allBranches detect: [ :branch | branch shortName = aGLHBranch name ]. + + gitRepository resetHardTo: gitBranch object. - aGLHBranch files: (gitRepository workingDirectory children - reject: [ :child | child basename = '.git' ] - thenCollect: [ :child | self buildGPHFileFor: child ]) - + aGLHBranch files: (gitBranch object tree entries collect: [ :entry | + self buildGPHFileFor: entry ]) ] { #category : #'import - commits' } From 6bab84ce6fdd7f4b4e6f16170ec5f92d10befc4e Mon Sep 17 00:00:00 2001 From: Benoit Verhaeghe Date: Wed, 26 Aug 2026 16:51:42 +0200 Subject: [PATCH 33/75] update test resources --- res/testResource.zip | Bin 43126 -> 50498 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/res/testResource.zip b/res/testResource.zip index fe92dba9075480db76f3dd2fbb0fac707f4c3b27..4937b6409d8ac9054963722d5c1e8d21c3ee79b0 100644 GIT binary patch delta 8903 zcmeG>cU+Ul`^frS%NsPi}dtt<+6ws9pHE7!6t^r{IX^AONM?)!hlmdSb zlwV9#cT+rjH}zKZ=9V;nf4Z}-RjF~^x0iQW)M%TYWjEIrGp@Ytv72i&?!K|wE$=yRo z5+8N`nR)X@`F2}MctdVaG=)YvvCMa-)h&f-8@Cpj46GB2$&&k}vAwI7McI{q<=-wVTS3RcU!tM+|_@)%zHIGzNNdxf0~J3oJpr? zsC1C`oH{$Lx`x$ph54>O)!Mw@?|*gvGt>OmEERg)wAx$!w~iml^%{Eo%18A{Nn-yq zwp8`CjM@VyLd5RAR@a?(ZJtQ{`qV>AjXxhQm>y!#?R?(pZmj0ML+Y$TkIlcT^*J2f zcwhRWe3ORK$^{h$oC8OXX5GEBVPj_Bjg1?Ane-veFh+X!lKp$PoH@8N^rDu9#o?ee z$Fc{DYi*4Bn@!-hpOOyw{o`8vDrn)M?xaOK%32ccnS;MQwjX&Ws?ss< z);F_XJzlRR_Rc-%dCR@Uxy>4TU1vFt1T8E^lbb69`&DCF6;E+T)3`-fyQ6mfvX&be`ALP z);(e+=Im+{H9NB5)vJi}HaG9AN$}BcGwe0aO+9A2!#<$U?<@)Rw8|5n=dvRP2 z7cJVdC^gZeqHguliv|k%vkKHI-iy~xIDfA6Jd5_LZs9}Uv?Ya&-&{G#2#MEfw&uLQ zR=O^{R;@sTWgfg~rahH;rywASW_coJh?Y|jKWO%WdacLj>6y9uTTUAdn%r9c{<*=V zqQYdU+_TAyRT<2A$ucGv^iEw%mS4KCSK(Nm(Q}Jz^}8pRJa_sa=X2kEh+TQo=ec># z${qI|K7iw=UuI0Ox__u;<=vh)Ln3p34;$ouaC#bU&bWR5iDS+nC(DMSZs zAQm@9hafQzq66Rhd}v0q!;GS61wxFVrHUMk7w3xBBMwLw1D+6f5yoq$VZ1WjpC$&Y z=uFIkt;9rBI`CMI5|o=D$)nDP+EjUXV1hZOxHDltyr&{6%m>TK5>bYOel++pQW1tr ziPB1%iHm(C=YC3wB@S*CgI-chVa;@)TAmU-U?@&gK}(4{Qm`wWq@8lH3m%~df2Tf| zN{I~*4+lMaj}_Ki+*J+j?Emve*$37v4vXGc@}`i(vazJTI7VIJy6oGdS3bskXqx)n zjkw3Bx(hRI#J;~1{xDL`*rU_4!`r*7b4F)d+ch)Gh;T~9lHh46UE-?J?8M&5HZL#l zSKgzt`1;)8e_DgxbDcKj7AD<*H+rQe{qENGuK00rd1iRi6m9);SCnRyb5u=KQznEC zJn#)jElNMWGx)cx;I{*So0D9nOq(Vq(h&OSvTfoxb^Gl^6cdiyC0MX)GrYs$(mR8Qt=K-rT?J^N1m>Twmmup=cc3;yq%{kElm+0 z^@+e!FJz`q#?aBnv%OU#z zR@BC;m56x}3liXDs*3k*wE8Z3@OQo63@@LKj)1zJUjOsve&@_*pYsmt?aimpxnWxO z$WS?^ub$_@-*qyx43Jhv=n?4Dk`9B!j3sd`T88l@NQ_DllptIv@6F3imskdrRX16$ zpeaaW4m_ctg-fAFAxZ!xy<^=3eK@F27m49j` zwkcO7Mj(R&FBK`mkE)V9Yu(`F;)&!{;3{0AqgF>okRJE%R!2sB!D=B+)M%JoGErS9 z%W*;q0*Pc|2S(fSS>f&=Q0g<+R23_dc7_aOE4e|Ll8J)u#&7&1W>DF%tB5ar+zyO| zvpIa>{N%>0{6>9s2MPJ8Do%48ZD+jLj*XfTrp7AGWsXE6f4#0)p@;Fr_n4h^1{A9tf$CVnO~`%G`&_e^l0_#`!xfi zMmcBpOz-={@vrXxk;B66$kI66B98;%B1gHw*0F(JrAnh+lHbe(0T_zHyPgC*xJ+J< z7fk4^Go9!1eCTY%gxO`fLM7M~s2e3J*!nQZOdO~ZqY%0PTnT5bH5FcUf)((|Ds6bg z)&#HYvlSnM^7dsy)_OzX)u$adQw7Zbz^m+>GI?Er8YzhN313 zP8H5NT8La6W<-xP&J-vWlSsn}ffUJ$aix;q#E~g*P(}{sEYO7U^IT}SpCFRPd5DDH zTSy@$EigAh?VZDpwz1)G?PG0Yxc0WzQ8rQbk&zr0%g&a?ii%*_L`Fp0u}P~Nb9$jR zQM^88Q1@MMLDeg_qNic8#K5#u^UXb@jCCH!-Cncz(e>~UH@}qOEmIRO4ICP}tKNRl zqBCW~n%UGR_HQ_AlkVO7$!BtGgsYFkyk~1SW>>Yjy{uNsYT9t?(#t9jOVQ3=@y;q4 zUweJ$#wE<=t*a;h`F+Hm)(ES!cewp~TP_|5dmMaWzw@t#m%r(MR1$pU(aj4{H!QEZ z6?d&li}X3|+IQ)G#wlmR&gQ)b+K0d8?0r_-S=T2$yCozS@ASb-Im=s6!8MK9RZ+pQ zQf8DTu!77Zt3ATJ+NjZO~Nl< zef2`xmgQ;3Vx#Cvy&8P(oZ8oTaBgo|olvMbLDtzx4vv*;4A(bq{zFw(M+=cf_oQTo3R$MlR%jMYHTSrG*$410*?V~wS z9IkCND>l-eZOyfdv2loGb3ZeTYE9`tiRgL0&Bvd0FuLw*PF6ELq+4(v%p5$n6B}kd zo?%EO%doFk>J!6I9$3PA38T?&GD_4FuF+Au*&M5ATYCqVJ;#n?XT!3MwBbhEb7HtQ ztmx=yj&)2lsz5tdEYEI+a6qYZD7DCLGZPQ*AQl%k#H+#8illApdL`Cq)H{BBSF` z*p=a;fG3;2o;)ybG79xGJ#{_@GeU(2E6Q+frY4;~OzB#uWHsIr{>TIcSBIjC{_;LG zdE(GFn;|rgK^%Z&FHFuv0|GI)BQpY+(x6|q8dU$mR=f3+>z;xTs{xnSH26GA5f=QQ zsDLv<*cy~DdwyVo>EeXdRiNx9MxhE5VRb$qya*H>aG{~g>8E2D_;aZe=;(nTelVv7 zK^HXVn4C2OXih*zo zHtNBKJSowEJU2N)zgkk6PmeW`l;_ctc@2*q%M6z%)M|Og#9hJz3l$;DYw`>fkR=x5 z#kMMs3Fc2EY;NbNjq6b1F@^RRww8)SEysM_@mMG1GeM&wVO=joAtAz_W!yy+XxfBR zFH}xl2_+V9njxqqG{10J&IC`C2%DVcnx3Ch;5mlyoI!1jh6mAn<oz1`mcr1ZRWIVsh7cjxk47>~z zAscW;`!3almkMP-wmM;7w89W+5|eecvN-jR2WgFgohv?GUDH~ z->!pKVj`sd7!z?b8S%q>T`Y;rAe6_@j11wh8&2sGMrZ;t;)Zjznvk_p29)U$HLHr< zz+*k4rcBAGnxI*qaK;0MFLIVOAe_;7VT5x+fjPKoK)5EAxB**3!u3vxE1(z==#*7% zAjF738&rbJ_v3QGxH)F+hhKpMteM7|eAi6bJ-E^8zUhC&wLG3n+`5*4=pSFgP zvcFWt1e)nsqyKc*9jRi*4s#&4gcz}Hfg{>8(mOxeW{t;y>x8tD#VkeRMwJ#MdIgbMFwhC)?elh;@*q%I9UOIP|558*Qx kdkhb~RU@`xlWCBfp`yXpLyU$`40-6EjRS=;vqB%OaSaB@z#-*@l3?{4pM zVYB_;)LUL31vwQMV~f2^2*BVbt0WT9RDi3MbmN;u|`#>=MkaTeh6 zYVa#X&X-q0yss3b{BRi1aRqTUgHX3x2?l-~6Y`i4t-J#jf(gXU0tW?O5-05!BVs5~ zz-2_#%=b~k0Px{#odOKY_`J=-7PO*2Fbg@N8oPx|aSGRsX}&dA30Ft@Lz>A8j3XRc z%ty0I3Jb*ErcMtyWRk&4K_cR3ffp(jRY+upWRkPY?3a?Gyicr$F||ywl)CHOMn~6hPo5%QrN=136~^G;2mjo?YTe}(zn=B% z{dV`msGo}VEog9_*XAUgD!Jx;Czg|y&KDHQ?$vCj^-&LPm1j!RnsobuCQ4t0Gjp~sjIea^2E0^o)DAO)EjH|RJBAjYM%PJ+T zQ>54v5$~HZTuJK`W8kW<#*VRsinSUrHhI%zI%Ucn!-aBfFnrgv_XQ{~PP?U6~|3*Z)n?jQa!kqdog|cWw8XHj|+BKeGZu4+`gG%f~PEPyVs+ zm;D{j7py+>TCQdJ3Rc()?lk{!g;gYh+e=kojtGJF4lk^1c4u38u{qJ?MdyUyaPS#9 z*&3^Nz#OB3osp3&gr7wgaqYu`Mb;dX#>BtG6ko7qU0dt=SUf&>R6^T!ZQ=@224IYqTvAzpVnPZ*JGDp#x3V){-j6e<5 zF&}$hvy!heR`A+r+QTi~KN??g!|#r6utIT`?!cw4kVq|s^|czf*%MF8gU@5#N1u`H zE3V>Kn}I506g)i{&t?>g!!0m<)eUDxS0(M>)roj&oCQ4BeC_Oj{gjek>nY4nKTCJo z`oQ1$83j+?os5IkItE};&CA%#+iDg%#5SjlwH4?=um%26`?=j2`_mY=VVeCte7;d@ z>J-A64S~+%p?Kp&W^syrJb0YHEaT7@+kEyb8sYyrzkcO!ClIejeIS_ZjW(n+fYmTZk6JU3|ET0m;u;PxS3 zSilQ_&$oKQO8UCY-6kP33Dle9uy&gqvQF!yDvpB2-}>{Qx{Z!NsSIb16d&IU{_*xF4-bmChy-A9F!}Mg?l23^Gm!LdgeW?t|(y57atS zrv~E*DcXHlrzA2S+SSx2lPx@CoD|V!q;?P12auV3L=Ej8uv#pGfdhdsXp)dlJ~C5G zsiagu8F}QK03{Db`anXP)$56%B()<@e+gueaYAI|d(!tEt;@lChlKnhM22;Tn?`1e zko702!r_CDm@!NZDWpt{;4T?+a5NZBbNw6}5e=V@QxMv#^-xDQ&FoR$7=vFMBt$1c z_3)j3H0*MfV+*3HIv3R;3Hmf&u0`Gcof0zN6Ls&){b^Q2LZQ23MT%-vkw_^j7#cM) zwh=kk#Wnb@#gE%w-6%x^{f#;|AGq8cNeX2wNv2&%TTk{fcA4yxod^m-EOg^0O1nf&I3hnFNh zNrqI`z_!mIFVq-7R*F?)l=cEp4Q~z{j-N{wuV|>QFHGoAur7lsono2>HyzymGyu$F zeH=Hr(xD;!I^C?IWdh=d)?Bh}*_x)F)Py6;Z3>2g7RD3j#H z+kD{B%_E0Am_*UFBz|HdYy1G5dC;=Af6&Rsw6iv9H%55 z%_iBIHtSpv_r-A{a8;{j}8tb%t)FL6HizrEL-DUjfxD;c_vo`#;gT}6iS+?#n)*hPDDIpVz?K-Ms zsf4_NrTw8X9->d_N#a5*Z~5_Z61xbIKOZk8m5Wgtv@8Kf{hl5TDPDr;&7M*cS%PTk z$#SACMd{O%rR2v_l>U8H0%RTcfm^4PIuoLLHRQmXh?ex0 zl8Y6HJnzk9vlGhBDM^nJ(e`tbX_G_|<6D$bj1NTZc?~J7M6{(g9=2cb1mT4M60{U; zEO{@1iuA;L8I6T6WNuK=z6}IrpM)%5Mw=&;?kEauWqOjzv18Ct>+jN}$!}wp%zc5_ zq{=?lq=Rp>P=fB$U>wxWEgs^c-L>3yx(H79VVg_!E`*0Gs4pq>K0LVxkG-&E^morV K4ySJ=`|H1YX#IZx From 33cde502d18bbc65aee4aea8b844b41276c5adb9 Mon Sep 17 00:00:00 2001 From: Benoit Verhaeghe Date: Wed, 26 Aug 2026 17:06:47 +0200 Subject: [PATCH 34/75] we use tests with test resource that is more direct to the approach --- .../GitLocalModelImporterTest.class.st | 60 ------------------- 1 file changed, 60 deletions(-) delete mode 100644 src/GitProjectHealth-Model-LocalImporter-Tests/GitLocalModelImporterTest.class.st diff --git a/src/GitProjectHealth-Model-LocalImporter-Tests/GitLocalModelImporterTest.class.st b/src/GitProjectHealth-Model-LocalImporter-Tests/GitLocalModelImporterTest.class.st deleted file mode 100644 index 8aa070bb..00000000 --- a/src/GitProjectHealth-Model-LocalImporter-Tests/GitLocalModelImporterTest.class.st +++ /dev/null @@ -1,60 +0,0 @@ -" -A GitLocalModelImporterTest is a test class for testing the behavior of GitLocalModelImporter -" -Class { - #name : #GitLocalModelImporterTest, - #superclass : #TestCase, - #instVars : [ - 'localImporter', - 'model', - 'root' - ], - #category : #'GitProjectHealth-Model-LocalImporter-Tests' -} - -{ #category : #running } -GitLocalModelImporterTest >> setUp [ - - super setUp. - localImporter := GitLocalModelImporter new. - model := GLHModel new. - localImporter glhModel: model. - root := FileSystem memory root. - (root / 'src') ensureCreateDirectory. - (root / 'src' / 'en') ensureCreateDirectory. - (root / 'src' / 'en' / 'test.st') ensureCreateFile -] - -{ #category : #tests } -GitLocalModelImporterTest >> testBuildGPHFileFor [ - - localImporter buildGPHFileFor: root. - self assert: (model allWithSubTypesOf: GLHFile) isNotEmpty -] - -{ #category : #tests } -GitLocalModelImporterTest >> testBuildGPHFileForMultipleTimes [ - "in case there are more and more files, we can parse several times. But we need to have only one time each file" - - localImporter buildGPHFileFor: root. - localImporter buildGPHFileFor: root. - self - assert: ((model allWithType: GLHFileDirectory) select: [ :f | - f path endsWith: '/src' ]) size - equals: 1 -] - -{ #category : #tests } -GitLocalModelImporterTest >> testBuildGPHFileForMultipleTimesAddItems [ - "in case there are more and more files, we can parse several times. But we need to have only one time each file" - - | size sizeAfter | - localImporter buildGPHFileFor: root. - size := (model allWithSubTypesOf: GLHFile) size. - (root / 'src' / 'fr') ensureCreateDirectory. - (root / 'src' / 'fr' / 'test.st') ensureCreateFile. - localImporter buildGPHFileFor: root. - sizeAfter := (model allWithSubTypesOf: GLHFile) size. - - self assert: size + 2 equals: sizeAfter -] From 688177eb1615f37884913ebc478e2cbe853db4ad Mon Sep 17 00:00:00 2001 From: Benoit Verhaeghe Date: Thu, 27 Aug 2026 11:33:59 +0200 Subject: [PATCH 35/75] fix test famix connector --- .../GPCGitToFamixConnector.class.st | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/GitProject-FamixConnector/GPCGitToFamixConnector.class.st b/src/GitProject-FamixConnector/GPCGitToFamixConnector.class.st index a824dd10..5004bb84 100644 --- a/src/GitProject-FamixConnector/GPCGitToFamixConnector.class.st +++ b/src/GitProject-FamixConnector/GPCGitToFamixConnector.class.st @@ -40,11 +40,13 @@ GPCGitToFamixConnector >> connectCommit: commit [ { #category : #accessing } GPCGitToFamixConnector >> connectDiff: diff [ "I connect a glhDiff with the famix model" - - (((famixModel allUsing: FamixTClass) reject: #isAnonymousClass) - , (famixModel allUsing: FamixTEnum) - , (famixModel allWithType: FamixJavaInterface) reject: [ :type | - type sourceAnchor isNil ]) + | col | + col := OrderedCollection new. + col addAll: ((famixModel allUsing: FamixTClass) reject: #isAnonymousClass). + col addAll: (famixModel allUsing: FamixTEnum). + col addAll: ((famixModel allWithType: FamixJavaInterface) reject: [ :type | + type sourceAnchor isNil ]). + col detect: [ :class | class sourceAnchor fileName endsWith: diff new_path ] ifFound: [ :class | class appliedDiffs add: diff ] From 71f2554b204370e36d3e00e68e4382a818b3244c Mon Sep 17 00:00:00 2001 From: anquetil Date: Fri, 28 Aug 2026 09:10:34 +0200 Subject: [PATCH 36/75] fix: source file name comparison --- .../GPCGitToFamixConnector.class.st | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/GitProject-FamixConnector/GPCGitToFamixConnector.class.st b/src/GitProject-FamixConnector/GPCGitToFamixConnector.class.st index a824dd10..922fc2b9 100644 --- a/src/GitProject-FamixConnector/GPCGitToFamixConnector.class.st +++ b/src/GitProject-FamixConnector/GPCGitToFamixConnector.class.st @@ -46,7 +46,7 @@ GPCGitToFamixConnector >> connectDiff: diff [ , (famixModel allWithType: FamixJavaInterface) reject: [ :type | type sourceAnchor isNil ]) detect: [ :class | - class sourceAnchor fileName endsWith: diff new_path ] + (self fullSourceAnchorPath: class) endsWith: diff new_path ] ifFound: [ :class | class appliedDiffs add: diff ] ] @@ -62,6 +62,13 @@ GPCGitToFamixConnector >> famixModel: anObject [ famixModel := anObject ] +{ #category : #accessing } +GPCGitToFamixConnector >> fullSourceAnchorPath: aSourcedEntity [ + "string of the full path name of aSourcedEntity source file, within the mooseModel rootFolder" + + ^(aSourcedEntity mooseModel rootFolder / (aSourcedEntity sourceAnchor fileName)) fullName +] + { #category : #accessing } GPCGitToFamixConnector >> glhProject [ From 97138aeae36471b7491816d52075a34a993bcc14 Mon Sep 17 00:00:00 2001 From: anquetil Date: Mon, 31 Aug 2026 11:37:21 +0200 Subject: [PATCH 37/75] refactor: automatic refactor of deprecated call --- .../GitLocalModelImporter.class.st | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/GitProjectHealth-Model-LocalImporter/GitLocalModelImporter.class.st b/src/GitProjectHealth-Model-LocalImporter/GitLocalModelImporter.class.st index 4295bd16..625cd36a 100644 --- a/src/GitProjectHealth-Model-LocalImporter/GitLocalModelImporter.class.st +++ b/src/GitProjectHealth-Model-LocalImporter/GitLocalModelImporter.class.st @@ -61,17 +61,17 @@ GitLocalModelImporter >> connectDiffs: diffs inside: aGPHRepository [ "could be refactor for a classic lazy accessor. But maybe this project can also be use for several repository. So to think about later" indexedFiles ifNil: [ - indexedFiles := (aGPHRepository allToScope: GLHFile) - collect: [ :el | el path -> el ] - as: Dictionary ]. + indexedFiles := (aGPHRepository allContainedEntitiesOfType: GLHFile) + collect: [ :el | el path -> el ] + as: Dictionary ]. diffs do: [ :diff | - indexedFiles - at: diff new_path - ifPresent: [ :gphFile | - diff addFile: gphFile. - "support for file renaming" - indexedFiles at: diff old_path put: gphFile ] - ifAbsent: [ "not supported yet" ] ] + indexedFiles + at: diff new_path + ifPresent: [ :gphFile | + diff addFile: gphFile. + "support for file renaming" + indexedFiles at: diff old_path put: gphFile ] + ifAbsent: [ "not supported yet" ] ] ] { #category : #'private - diffs' } From b361dbf677fb526d57b6351f4bf5ef05e0a295ee Mon Sep 17 00:00:00 2001 From: anquetil Date: Mon, 31 Aug 2026 11:56:52 +0200 Subject: [PATCH 38/75] fix: made safeguard against empty sourceAnchor more generic in GPCGitToFamixConnector --- .../GPCGitToFamixConnector.class.st | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/GitProject-FamixConnector/GPCGitToFamixConnector.class.st b/src/GitProject-FamixConnector/GPCGitToFamixConnector.class.st index 134b238d..b6d85dc5 100644 --- a/src/GitProject-FamixConnector/GPCGitToFamixConnector.class.st +++ b/src/GitProject-FamixConnector/GPCGitToFamixConnector.class.st @@ -44,11 +44,12 @@ GPCGitToFamixConnector >> connectDiff: diff [ col := OrderedCollection new. col addAll: ((famixModel allUsing: FamixTClass) reject: #isAnonymousClass). col addAll: (famixModel allUsing: FamixTEnum). - col addAll: ((famixModel allWithType: FamixJavaInterface) reject: [ :type | - type sourceAnchor isNil ]). + col addAll: (famixModel allWithType: FamixJavaInterface). col detect: [ :class | - (self fullSourceAnchorPath: class) endsWith: diff new_path ] + (self fullSourceAnchorPath: class) + endsWith: diff new_path + ] ifFound: [ :class | class appliedDiffs add: diff ] ] @@ -68,7 +69,12 @@ GPCGitToFamixConnector >> famixModel: anObject [ GPCGitToFamixConnector >> fullSourceAnchorPath: aSourcedEntity [ "string of the full path name of aSourcedEntity source file, within the mooseModel rootFolder" - ^(aSourcedEntity mooseModel rootFolder / (aSourcedEntity sourceAnchor fileName)) fullName + ^aSourcedEntity sourceAnchor + ifNil: [ '' ] + ifNotNil: [ :sourceAnchor | + (aSourcedEntity mooseModel rootFolder / (sourceAnchor fileName)) + fullName + ] ] { #category : #accessing } From 107be7494ecb1d8c9ed7103e9450e238fe74afab Mon Sep 17 00:00:00 2001 From: anquetil Date: Mon, 31 Aug 2026 13:25:33 +0200 Subject: [PATCH 39/75] fix: rolling back automatic deprecated rewritting as it does not compile in Moose-12 --- .../GitLocalModelImporter.class.st | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/GitProjectHealth-Model-LocalImporter/GitLocalModelImporter.class.st b/src/GitProjectHealth-Model-LocalImporter/GitLocalModelImporter.class.st index 625cd36a..4295bd16 100644 --- a/src/GitProjectHealth-Model-LocalImporter/GitLocalModelImporter.class.st +++ b/src/GitProjectHealth-Model-LocalImporter/GitLocalModelImporter.class.st @@ -61,17 +61,17 @@ GitLocalModelImporter >> connectDiffs: diffs inside: aGPHRepository [ "could be refactor for a classic lazy accessor. But maybe this project can also be use for several repository. So to think about later" indexedFiles ifNil: [ - indexedFiles := (aGPHRepository allContainedEntitiesOfType: GLHFile) - collect: [ :el | el path -> el ] - as: Dictionary ]. + indexedFiles := (aGPHRepository allToScope: GLHFile) + collect: [ :el | el path -> el ] + as: Dictionary ]. diffs do: [ :diff | - indexedFiles - at: diff new_path - ifPresent: [ :gphFile | - diff addFile: gphFile. - "support for file renaming" - indexedFiles at: diff old_path put: gphFile ] - ifAbsent: [ "not supported yet" ] ] + indexedFiles + at: diff new_path + ifPresent: [ :gphFile | + diff addFile: gphFile. + "support for file renaming" + indexedFiles at: diff old_path put: gphFile ] + ifAbsent: [ "not supported yet" ] ] ] { #category : #'private - diffs' } From a93c667e36c80535b9ecb3a64e895fe3b01cc7b8 Mon Sep 17 00:00:00 2001 From: Benoit Verhaeghe Date: Mon, 31 Aug 2026 16:29:59 +0200 Subject: [PATCH 40/75] use loadAllBranches instead of a wrongly saying loadAllBranchesSinceUntil --- .../ActiveBranchesProjectMetric.class.st | 4 +--- .../InactiveBranchesProjectMetric.class.st | 4 +--- .../ProjectMetric.class.st | 16 +++++++--------- 3 files changed, 9 insertions(+), 15 deletions(-) diff --git a/src/GitLabHealth-Model-Analysis/ActiveBranchesProjectMetric.class.st b/src/GitLabHealth-Model-Analysis/ActiveBranchesProjectMetric.class.st index d02f6aaa..92a6d6b2 100644 --- a/src/GitLabHealth-Model-Analysis/ActiveBranchesProjectMetric.class.st +++ b/src/GitLabHealth-Model-Analysis/ActiveBranchesProjectMetric.class.st @@ -41,7 +41,5 @@ ActiveBranchesProjectMetric >> description [ { #category : #loading } ActiveBranchesProjectMetric >> load [ - ^ projectBranches := self - loadBranchesSince: (period at: #since) - until: (period at: #until) + ^ projectBranches := self loadAllBranches ] diff --git a/src/GitLabHealth-Model-Analysis/InactiveBranchesProjectMetric.class.st b/src/GitLabHealth-Model-Analysis/InactiveBranchesProjectMetric.class.st index 62bc8ec7..7df4b053 100644 --- a/src/GitLabHealth-Model-Analysis/InactiveBranchesProjectMetric.class.st +++ b/src/GitLabHealth-Model-Analysis/InactiveBranchesProjectMetric.class.st @@ -39,7 +39,5 @@ InactiveBranchesProjectMetric >> description [ { #category : #loading } InactiveBranchesProjectMetric >> load [ - ^ projectBranches := self - loadBranchesSince: (period at: #since) - until: (period at: #until) + ^ projectBranches := self loadAllBranches ] diff --git a/src/GitLabHealth-Model-Analysis/ProjectMetric.class.st b/src/GitLabHealth-Model-Analysis/ProjectMetric.class.st index a5e2b9de..47f87ff5 100644 --- a/src/GitLabHealth-Model-Analysis/ProjectMetric.class.st +++ b/src/GitLabHealth-Model-Analysis/ProjectMetric.class.st @@ -18,23 +18,21 @@ ProjectMetric class >> isAbstract [ ^ self == ProjectMetric ] -{ #category : #loading } -ProjectMetric >> loadAllTags [ - - ^ glhImporter importTagsForProject: project -] - { #category : #load } -ProjectMetric >> loadBranchesSince: since until: until [ +ProjectMetric >> loadAllBranches [ | branches | branches := glhImporter importBranchesOf: project. - - branches do: [ :branch | glhImporter importRefCommitOfBranch: branch ]. ^ branches ] +{ #category : #loading } +ProjectMetric >> loadAllTags [ + + ^ glhImporter importTagsForProject: project +] + { #category : #loading } ProjectMetric >> loadCommitsSince: since until: until [ From 006ef945c137e0a12ea56c5409cf3cb2752f5951 Mon Sep 17 00:00:00 2001 From: Benoit Verhaeghe Date: Mon, 31 Aug 2026 16:34:43 +0200 Subject: [PATCH 41/75] implementing a working version of load branches since until --- src/GitLabHealth-Model-Analysis/ProjectMetric.class.st | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/GitLabHealth-Model-Analysis/ProjectMetric.class.st b/src/GitLabHealth-Model-Analysis/ProjectMetric.class.st index 47f87ff5..7191d8d6 100644 --- a/src/GitLabHealth-Model-Analysis/ProjectMetric.class.st +++ b/src/GitLabHealth-Model-Analysis/ProjectMetric.class.st @@ -33,6 +33,13 @@ ProjectMetric >> loadAllTags [ ^ glhImporter importTagsForProject: project ] +{ #category : #load } +ProjectMetric >> loadBranchesSince: since until: until [ + + ^ self loadAllBranches select: [ :branch | + branch commits first created_at between: since and: until ] +] + { #category : #loading } ProjectMetric >> loadCommitsSince: since until: until [ From 1a9b16a56495a196b341444cb3621bfcaf7a4f29 Mon Sep 17 00:00:00 2001 From: Benoit Verhaeghe Date: Mon, 31 Aug 2026 16:49:40 +0200 Subject: [PATCH 42/75] use committed date that is more accurate to last update of commit as far as i understand --- src/GitLabHealth-Model-Analysis/ProjectMetric.class.st | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/GitLabHealth-Model-Analysis/ProjectMetric.class.st b/src/GitLabHealth-Model-Analysis/ProjectMetric.class.st index 7191d8d6..3a3db039 100644 --- a/src/GitLabHealth-Model-Analysis/ProjectMetric.class.st +++ b/src/GitLabHealth-Model-Analysis/ProjectMetric.class.st @@ -37,7 +37,7 @@ ProjectMetric >> loadAllTags [ ProjectMetric >> loadBranchesSince: since until: until [ ^ self loadAllBranches select: [ :branch | - branch commits first created_at between: since and: until ] + branch commits first committed_date between: since and: until ] ] { #category : #loading } From a1c423f661e0ab1d04cf122ba68c062c0b42cd60 Mon Sep 17 00:00:00 2001 From: anquetil Date: Tue, 1 Sep 2026 16:22:45 +0200 Subject: [PATCH 43/75] fix: fix #274 --- .../GLHGroupVisualization.class.st | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/GitLabHealth-Model-Visualization/GLHGroupVisualization.class.st b/src/GitLabHealth-Model-Visualization/GLHGroupVisualization.class.st index 79cb9bd1..aff0bbcd 100644 --- a/src/GitLabHealth-Model-Visualization/GLHGroupVisualization.class.st +++ b/src/GitLabHealth-Model-Visualization/GLHGroupVisualization.class.st @@ -132,7 +132,7 @@ GLHGroupVisualization >> forGroup: aGLHGroups [ canvas: c; shapes: c nodes; connectFrom: #group. - RSHorizontalVanDerPloegTreeLayout new on: c nodes. + RSHorizontalTreeLayout new on: c nodes. legend := self createLegend. legend container: c. From aa279107c2bf828b8bb4c33c92854e0b8cad5e14 Mon Sep 17 00:00:00 2001 From: Benoit Verhaeghe Date: Tue, 1 Sep 2026 16:57:48 +0200 Subject: [PATCH 44/75] remove useless comment --- src/GitLabHealth-Model-Importer/GitlabModelImporter.class.st | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/GitLabHealth-Model-Importer/GitlabModelImporter.class.st b/src/GitLabHealth-Model-Importer/GitlabModelImporter.class.st index 456a1864..2f9f44c0 100644 --- a/src/GitLabHealth-Model-Importer/GitlabModelImporter.class.st +++ b/src/GitLabHealth-Model-Importer/GitlabModelImporter.class.st @@ -873,9 +873,6 @@ GitlabModelImporter >> importCommitsFromTag: fromTag toTag: toTag [ GitlabModelImporter >> importCommitsOfBranch: aGLHBranch [ | commits | - " result := self glhApi - commitsOfProject: aGLHBranch repository project id - forRefName: aGLHBranch name." commits := self importCommitsOfBranch: aGLHBranch since: withCommitsSince. From 1faefae55601679a24cd205f53999e2f09315faa Mon Sep 17 00:00:00 2001 From: anquetil Date: Tue, 1 Sep 2026 17:07:18 +0200 Subject: [PATCH 45/75] fix: Corrected import of github projects, the status of the pipeline is in #collection (when #status = 'completed') --- src/GitHubHealth-Model-Importer/GithubModelImporter.class.st | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/GitHubHealth-Model-Importer/GithubModelImporter.class.st b/src/GitHubHealth-Model-Importer/GithubModelImporter.class.st index d58bb1a5..78982353 100644 --- a/src/GitHubHealth-Model-Importer/GithubModelImporter.class.st +++ b/src/GitHubHealth-Model-Importer/GithubModelImporter.class.st @@ -271,7 +271,6 @@ GithubModelImporter >> configureReaderForPipeline: reader [ reader for: GLHPipeline do: [ :mapping | "mapping mapInstVar: #status to: #conclusion." - (mapping mapInstVar: #created_at) valueSchema: DateAndTime. (mapping mapInstVar: #updated_at) valueSchema: DateAndTime. @@ -286,8 +285,7 @@ GithubModelImporter >> configureReaderForPipeline: reader [ mapProperty: #conclusion getter: [ :object | #ignore ] setter: [ :object :value | - (object status isNil and: [ value isNotNil ]) ifTrue: [ - object status: value ] ]. + value ifNotNil: [ object status: value ] ]. mapping mapProperty: #run_started_at From c25fa04afe307484381c5dfb1422f32e957dad5d Mon Sep 17 00:00:00 2001 From: Benoit Verhaeghe Date: Tue, 1 Sep 2026 17:10:29 +0200 Subject: [PATCH 46/75] remove deprecated method --- .../GitAnalyzerTest.class.st | 33 ++++----- .../GitlabModelImporter.class.st | 67 ------------------- 2 files changed, 18 insertions(+), 82 deletions(-) diff --git a/src/GitLabHealth-Model-Analysis-Tests/GitAnalyzerTest.class.st b/src/GitLabHealth-Model-Analysis-Tests/GitAnalyzerTest.class.st index 9eb51a2c..fb8e7e77 100644 --- a/src/GitLabHealth-Model-Analysis-Tests/GitAnalyzerTest.class.st +++ b/src/GitLabHealth-Model-Analysis-Tests/GitAnalyzerTest.class.st @@ -60,17 +60,19 @@ GitAnalyzerTest >> testAnalyseAmandment [ mondayCommit -> tuesdayCommit -> wednesdayCommit " wednesdayCommit := glhImporter - importCommitOfProject: projects first - withId: - 'f298ec312f81032b611a890f993d98096ab3bbc1'. + importCommit: + 'f298ec312f81032b611a890f993d98096ab3bbc1' + ofProject: projects first. mondayCommit := glhImporter - importCommitOfProject: projects first - withId: '8a6f665118e6fcfa468d7cce1a7e944ba65812ab'. + importCommit: + '8a6f665118e6fcfa468d7cce1a7e944ba65812ab' + ofProject: projects first. tuesdayCommit := glhImporter - importCommitOfProject: projects first - withId: '2c7308c727560dd425ef78f9a95ca7650d164bed'. + importCommit: + '2c7308c727560dd425ef78f9a95ca7650d164bed' + ofProject: projects first. myCommits := { mondayCommit. @@ -135,18 +137,19 @@ GitAnalyzerTest >> testChurnMetric [ "commit chain: mondayCommit -> tuesdayCommit -> wednesdayCommit " wednesdayCommit := glhImporter - importCommitOfProject: projects first - withId: - 'f298ec312f81032b611a890f993d98096ab3bbc1'. + importCommit: + 'f298ec312f81032b611a890f993d98096ab3bbc1' + ofProject: projects first. mondayCommit := glhImporter - importCommitOfProject: projects first - withId: '8a6f665118e6fcfa468d7cce1a7e944ba65812ab'. + importCommit: + '8a6f665118e6fcfa468d7cce1a7e944ba65812ab' + ofProject: projects first. tuesdayCommit := glhImporter - importCommitOfProject: projects first - withId: '2c7308c727560dd425ef78f9a95ca7650d164bed'. - + importCommit: + '2c7308c727560dd425ef78f9a95ca7650d164bed' + ofProject: projects first. myCommits := { mondayCommit. diff --git a/src/GitLabHealth-Model-Importer/GitlabModelImporter.class.st b/src/GitLabHealth-Model-Importer/GitlabModelImporter.class.st index 2f9f44c0..da44303f 100644 --- a/src/GitLabHealth-Model-Importer/GitlabModelImporter.class.st +++ b/src/GitLabHealth-Model-Importer/GitlabModelImporter.class.st @@ -766,73 +766,6 @@ GitlabModelImporter >> importCommit: aCommitID ofProject: aGLHProject [ ^ parsedResult ] -{ #category : #'import - commits' } -GitlabModelImporter >> importCommitOfProject: anProject withId: anID [ - - | commit result | -self - deprecated: 'Use importCommit:ofProject: instead of current one' - on: '27 August 2025' - in: 'MSR 1.3.0 ' - transformWith: '`@rcv importCommitOfProject: `arg1 withId: `arg2' -> '`@rcv importCommit: `arg2 ofProject: `arg1'. - anID ifNil: [ ^ nil ]. - - ('looking for commit ' , anID printString , ' in project : ' - , anProject id printString) recordInfo. - - commit := (self - detectEntityType: GLHCommit - overAttribut: #id - equalTo: anID) ifNil: [ - result := self repoApi commits - get: anID - inProject: anProject id. - commit := (self parseCommitsResult: '[' , result , ']') - first. - - commit := self glhModel - add: commit - unless: self blockOnIdEquality. - commit repository: anProject repository. - - commit ]. - - self withCommitDiffs ifTrue: [ self importDiffOfCommit: commit ]. - - ^ commit -] - -{ #category : #'import - commits' } -GitlabModelImporter >> importCommits: aGLHProject [ - "limited to the last 20 commits" - - | results parsedResults params | - self - deprecated: - 'Use importLatestCommitsOfProject: instead of current one' - on: '19 September 2025' - in: - 'Pharo-12.0.0+SNAPSHOT.build.1571.sha.cf5fcd22e66957962c97dffc58b0393b7f368147 (64 Bit)'. - - params := { (#with_stats -> 'true') } asDictionary. - results := self repoApi commits - getByPage: 1 - perPage: 20 - inProject: aGLHProject id - withParams: params. - - parsedResults := self parseCommitsResult: results. - self glhModel addAll: parsedResults unless: self blockOnIdEquality. - - parsedResults do: [ :commit | - commit repository: aGLHProject repository ]. - - self withCommitDiffs ifTrue: [ - parsedResults do: [ :commit | self importDiffOfCommit: commit ] ]. - - ^ parsedResults -] - { #category : #'import - commits' } GitlabModelImporter >> importCommitsFollowing: aCommit upToDays: aNumberOfDay [ "import the 'n' commits of a project starting from an initial 'aCommit' commit. From 8f9bfd1b6188fd711bb93cbe4c5c5782c1c0de85 Mon Sep 17 00:00:00 2001 From: Benoit Verhaeghe Date: Tue, 1 Sep 2026 17:14:26 +0200 Subject: [PATCH 47/75] clean deprecated methods --- .../GithubModelImporter.class.st | 28 ++++++++++++++++--- .../ProjectMetric.class.st | 2 +- .../GitModelImporter.class.st | 6 ++++ .../TGitModelImporterTest.trait.st | 2 +- 4 files changed, 32 insertions(+), 6 deletions(-) diff --git a/src/GitHubHealth-Model-Importer/GithubModelImporter.class.st b/src/GitHubHealth-Model-Importer/GithubModelImporter.class.st index d58bb1a5..695b457d 100644 --- a/src/GitHubHealth-Model-Importer/GithubModelImporter.class.st +++ b/src/GitHubHealth-Model-Importer/GithubModelImporter.class.st @@ -71,8 +71,8 @@ GithubModelImporter >> completeImportedProject: aGLHProject [ aGLHProject repository: GLHRepository new. self glhModel add: aGLHProject repository. - self importPipelinesOfProject: aGLHProject. - self importBranchesOf: aGLHProject. + self importAllPipelinesOfProject: aGLHProject. + self importBranchesOfProject: aGLHProject. self withCommitsSince ifNotNil: [ :withCommitSince | "If not nil, it means we have to import commit" | commits | commits := self importCommitsOfProject: aGLHProject. @@ -343,6 +343,20 @@ GithubModelImporter >> ensureGroupNamed: aGroupName [ ifNone: [ self glhModel newGroupNamed: aGroupName ] ] +{ #category : #'import - pipelines' } +GithubModelImporter >> importAllPipelinesOfProject: project [ + + "add the pipeline (actions runs) in the project" + + | pipelinesResult ghApiPipelineOverview | + pipelinesResult := self repoApi actions + getLatestForRepo: project name + ofOwner: project group name. + ghApiPipelineOverview := self parsePipelinesResult: pipelinesResult. + ghApiPipelineOverview workflow_runs do: [ :pipeline | + project addPipeline: pipeline ] +] + { #category : #'import - branches' } GithubModelImporter >> importBranchesOf: aGLHProject [ "add the pipeline (actions runs) in the project" @@ -817,15 +831,21 @@ GithubModelImporter >> importPipelinesOfMergeRequest: aGLHMergeRequest [ { #category : #'import - pipelines' } GithubModelImporter >> importPipelinesOfProject: project [ - "add the pipeline (actions runs) in the project" | pipelinesResult ghApiPipelineOverview | + self + deprecated: + 'Use importAllPipelinesOfProject: instead of current one' + on: '1 September 2026' + in: + 'Pharo-12.0.0+SNAPSHOT.build.1571.sha.cf5fcd22e66957962c97dffc58b0393b7f368147 (64 Bit)'. + pipelinesResult := self repoApi actions getLatestForRepo: project name ofOwner: project group name. ghApiPipelineOverview := self parsePipelinesResult: pipelinesResult. - ghApiPipelineOverview workflow_runs do: [ :pipeline | + ghApiPipelineOverview workflow_runs do: [ :pipeline | project addPipeline: pipeline ] ] diff --git a/src/GitLabHealth-Model-Analysis/ProjectMetric.class.st b/src/GitLabHealth-Model-Analysis/ProjectMetric.class.st index 3a3db039..d7367f21 100644 --- a/src/GitLabHealth-Model-Analysis/ProjectMetric.class.st +++ b/src/GitLabHealth-Model-Analysis/ProjectMetric.class.st @@ -22,7 +22,7 @@ ProjectMetric class >> isAbstract [ ProjectMetric >> loadAllBranches [ | branches | - branches := glhImporter importBranchesOf: project. + branches := glhImporter importBranchesOfProject: project. branches do: [ :branch | glhImporter importRefCommitOfBranch: branch ]. ^ branches ] diff --git a/src/GitProjectHealth-Model-Importer/GitModelImporter.class.st b/src/GitProjectHealth-Model-Importer/GitModelImporter.class.st index 7e0e8478..8fd7c502 100644 --- a/src/GitProjectHealth-Model-Importer/GitModelImporter.class.st +++ b/src/GitProjectHealth-Model-Importer/GitModelImporter.class.st @@ -430,6 +430,12 @@ GitModelImporter >> glhModel: anObject [ glhModel := anObject ] +{ #category : #'import - pipelines' } +GitModelImporter >> importAllPipelinesOfProject: aGLHProject [ + self subclassResponsibility + +] + { #category : #'import - commits' } GitModelImporter >> importAndLoadLatestsCommitsOfProject: aGLHProject [ diff --git a/src/GitProjectHealth-Model-Importer/TGitModelImporterTest.trait.st b/src/GitProjectHealth-Model-Importer/TGitModelImporterTest.trait.st index 72d797a7..b3f9df23 100644 --- a/src/GitProjectHealth-Model-Importer/TGitModelImporterTest.trait.st +++ b/src/GitProjectHealth-Model-Importer/TGitModelImporterTest.trait.st @@ -237,7 +237,7 @@ TGitModelImporterTest >> defaultProject [ TGitModelImporterTest >> testImportBranchesOf [ | collection element | - collection := importer importBranchesOf: self defaultProject. + collection := importer importBranchesOfProject: self defaultProject. self assert: collection isCollection. From 9dab92ce7bed3106436db09880e34459917842f8 Mon Sep 17 00:00:00 2001 From: Benoit Verhaeghe Date: Thu, 3 Sep 2026 13:37:52 +0200 Subject: [PATCH 48/75] Add MIT License file --- LICENSE | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 LICENSE diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..7f02b553 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 Moosetechnology + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. From faa6561ba4ff4293412483d5ff037f80d4d67ca3 Mon Sep 17 00:00:00 2001 From: Benoit Verhaeghe Date: Thu, 3 Sep 2026 18:42:14 +0200 Subject: [PATCH 49/75] explore all branches and avoid re-exploring commits already explored (branch already explored) --- .../GitLocalModelImporter.class.st | 57 +++++++++++++------ 1 file changed, 39 insertions(+), 18 deletions(-) diff --git a/src/GitProjectHealth-Model-LocalImporter/GitLocalModelImporter.class.st b/src/GitProjectHealth-Model-LocalImporter/GitLocalModelImporter.class.st index 4295bd16..7ef280f0 100644 --- a/src/GitProjectHealth-Model-LocalImporter/GitLocalModelImporter.class.st +++ b/src/GitProjectHealth-Model-LocalImporter/GitLocalModelImporter.class.st @@ -174,30 +174,51 @@ GitLocalModelImporter >> importCommitsOfRepository: aGLHRepository [ { #category : #'import - commits' } GitLocalModelImporter >> importCommitsOfRepository: aGPHRepository since: sinceDate until: untilDate [ - | i revwalk commits | - i := 0. - revwalk := LGitRevwalk of: (self gitRepositoryOf: aGPHRepository). + | revwalk commits alreadyExploredCommit | commits := OrderedCollection new. - revwalk pushHead. - [ - revwalk hasNext and: [ - commits isEmpty or: [ commits last committed_date > sinceDate ] ] ] - whileTrue: [ - | gitCommit glhCommit | - i := i + 1. + "to stop when already explored" + alreadyExploredCommit := aGPHRepository commits + collect: [ :commit | commit id ] + as: Set. + (self gitRepositoryOf: aGPHRepository) allBranches + select: #isLocal + thenDo: [ :branch | + | gphBranch shouldContinue lastBranchCommits | + gphBranch := (glhModel allWithType: GLHBranch) + detect: [ :gBranch | branch shortName = gBranch name ] + ifNone: [ nil ]. + + revwalk := LGitRevwalk of: (self gitRepositoryOf: aGPHRepository). + revwalk pushReference: branch. + lastBranchCommits := nil. + shouldContinue := true. + [ + | glhCommit gitCommit | gitCommit := revwalk next. glhCommit := self importCommit: gitCommit. + + lastBranchCommits := glhCommit. commits add: glhCommit. - glhCommit cacheAt: #importerOrigin put: gitCommit ]. + alreadyExploredCommit add: glhCommit id. + glhCommit cacheAt: #importerOrigin put: gitCommit. - self chainsCommitsFrom: commits. + gphBranch commits ifEmpty: [ gphBranch addCommit: glhCommit ] ] + doWhileTrue: [ + revwalk hasNext and: [ + lastBranchCommits committed_date > sinceDate and: [ + (lastBranchCommits parent_ids anySatisfy: [ :id | + alreadyExploredCommit includes: id ]) not ] ] ]. - self withCommitDiffs ifTrue: [ - | diffs | - diffs := commits flatCollect: [ :commit | - self importDiffOfCommit: commit ]. - self withFiles ifTrue: [ - self connectDiffs: diffs inside: aGPHRepository ] ]. + + self withCommitDiffs ifTrue: [ + | diffs | + diffs := commits flatCollect: [ :commit | + self importDiffOfCommit: commit ]. + self withFiles ifTrue: [ + self connectDiffs: diffs inside: aGPHRepository ] ] ]. + + + self chainsCommitsFrom: commits. ^ commits ] From cae228fcb9b9f93fe36921ddb89cbffa1fb6a418 Mon Sep 17 00:00:00 2001 From: Benoit Verhaeghe Date: Fri, 4 Sep 2026 08:16:06 +0200 Subject: [PATCH 50/75] don't mess with branch name as pharo do --- .../GitLocalModelImporter.class.st | 4 ++-- .../LGitReference.extension.st | 9 +++++++++ 2 files changed, 11 insertions(+), 2 deletions(-) create mode 100644 src/GitProjectHealth-Model-LocalImporter/LGitReference.extension.st diff --git a/src/GitProjectHealth-Model-LocalImporter/GitLocalModelImporter.class.st b/src/GitProjectHealth-Model-LocalImporter/GitLocalModelImporter.class.st index 7ef280f0..20dde6fb 100644 --- a/src/GitProjectHealth-Model-LocalImporter/GitLocalModelImporter.class.st +++ b/src/GitProjectHealth-Model-LocalImporter/GitLocalModelImporter.class.st @@ -120,7 +120,7 @@ GitLocalModelImporter >> importBranchesOfRepository: aGLHRepository [ select: [ :branch | branch isBranch ] thenCollect: [ :branch | GLHBranch new - name: branch shortName; + name: branch gitName; sha: branch targetId hexString; yourself ]. aGLHRepository branches addAll: branches. @@ -185,7 +185,7 @@ GitLocalModelImporter >> importCommitsOfRepository: aGPHRepository since: sinceD thenDo: [ :branch | | gphBranch shouldContinue lastBranchCommits | gphBranch := (glhModel allWithType: GLHBranch) - detect: [ :gBranch | branch shortName = gBranch name ] + detect: [ :gBranch | branch gitName = gBranch name ] ifNone: [ nil ]. revwalk := LGitRevwalk of: (self gitRepositoryOf: aGPHRepository). diff --git a/src/GitProjectHealth-Model-LocalImporter/LGitReference.extension.st b/src/GitProjectHealth-Model-LocalImporter/LGitReference.extension.st new file mode 100644 index 00000000..40b25a3b --- /dev/null +++ b/src/GitProjectHealth-Model-LocalImporter/LGitReference.extension.st @@ -0,0 +1,9 @@ +Extension { #name : #LGitReference } + +{ #category : #'*GitProjectHealth-Model-LocalImporter' } +LGitReference >> gitName [ + + ^ self isLocal + ifTrue: [ self name withoutPrefix: 'refs/heads/' ] + ifFalse: [ self name withoutPrefix: 'refs/remotes/' ] +] From 569f3a6cb227e12d34a6275f3f3a92dd1f0a9ff4 Mon Sep 17 00:00:00 2001 From: Benoit Verhaeghe Date: Fri, 4 Sep 2026 13:34:57 +0200 Subject: [PATCH 51/75] need to chain commit early to enable diff parsing --- .../GitLocalModelImporter.class.st | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/GitProjectHealth-Model-LocalImporter/GitLocalModelImporter.class.st b/src/GitProjectHealth-Model-LocalImporter/GitLocalModelImporter.class.st index 20dde6fb..54dd58b9 100644 --- a/src/GitProjectHealth-Model-LocalImporter/GitLocalModelImporter.class.st +++ b/src/GitProjectHealth-Model-LocalImporter/GitLocalModelImporter.class.st @@ -184,6 +184,7 @@ GitLocalModelImporter >> importCommitsOfRepository: aGPHRepository since: sinceD select: #isLocal thenDo: [ :branch | | gphBranch shouldContinue lastBranchCommits | + gphBranch := (glhModel allWithType: GLHBranch) detect: [ :gBranch | branch gitName = gBranch name ] ifNone: [ nil ]. @@ -207,8 +208,9 @@ GitLocalModelImporter >> importCommitsOfRepository: aGPHRepository since: sinceD revwalk hasNext and: [ lastBranchCommits committed_date > sinceDate and: [ (lastBranchCommits parent_ids anySatisfy: [ :id | - alreadyExploredCommit includes: id ]) not ] ] ]. + alreadyExploredCommit includes: id ]) not ] ] ]. + self chainsCommitsFrom: commits. self withCommitDiffs ifTrue: [ | diffs | @@ -218,8 +220,6 @@ GitLocalModelImporter >> importCommitsOfRepository: aGPHRepository since: sinceD self connectDiffs: diffs inside: aGPHRepository ] ] ]. - self chainsCommitsFrom: commits. - ^ commits ] From f587eaf64492e3bed10c837e989edba073152369 Mon Sep 17 00:00:00 2001 From: Benoit Verhaeghe Date: Fri, 4 Sep 2026 13:46:56 +0200 Subject: [PATCH 52/75] only compute diff once (even in multibranch cases) --- ...ocalModelImporterWithTestResourceTest.class.st | 7 +++++-- .../GitLocalModelImporter.class.st | 15 +++++++-------- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/src/GitProjectHealth-Model-LocalImporter-Tests/GitLocalModelImporterWithTestResourceTest.class.st b/src/GitProjectHealth-Model-LocalImporter-Tests/GitLocalModelImporterWithTestResourceTest.class.st index b38a8372..50d68802 100644 --- a/src/GitProjectHealth-Model-LocalImporter-Tests/GitLocalModelImporterWithTestResourceTest.class.st +++ b/src/GitProjectHealth-Model-LocalImporter-Tests/GitLocalModelImporterWithTestResourceTest.class.st @@ -51,12 +51,15 @@ GitLocalModelImporterWithTestResourceTest >> testFirstCommitHasADiff [ { #category : #tests } GitLocalModelImporterWithTestResourceTest >> testHasRenamedField [ - | file | + | file renameCommit | localImporter importRepository: repository. file := (model allWithType: GLHFileBlob) detect: [ :fileBlob | fileBlob path endsWith: 'renamed-hello.st' ]. self assert: file diffs size equals: 3. - self assert: file diffs second renamed_file + renameCommit := (model allWithType: GLHCommit) detect: [ :commit | + commit id + = '505cd202d27777c018af5590a44689928ea55430' ]. + self assert: renameCommit diffs anyOne renamed_file ] { #category : #tests } diff --git a/src/GitProjectHealth-Model-LocalImporter/GitLocalModelImporter.class.st b/src/GitProjectHealth-Model-LocalImporter/GitLocalModelImporter.class.st index 54dd58b9..7c55f4c3 100644 --- a/src/GitProjectHealth-Model-LocalImporter/GitLocalModelImporter.class.st +++ b/src/GitProjectHealth-Model-LocalImporter/GitLocalModelImporter.class.st @@ -184,7 +184,6 @@ GitLocalModelImporter >> importCommitsOfRepository: aGPHRepository since: sinceD select: #isLocal thenDo: [ :branch | | gphBranch shouldContinue lastBranchCommits | - gphBranch := (glhModel allWithType: GLHBranch) detect: [ :gBranch | branch gitName = gBranch name ] ifNone: [ nil ]. @@ -210,14 +209,14 @@ GitLocalModelImporter >> importCommitsOfRepository: aGPHRepository since: sinceD (lastBranchCommits parent_ids anySatisfy: [ :id | alreadyExploredCommit includes: id ]) not ] ] ]. - self chainsCommitsFrom: commits. + self chainsCommitsFrom: commits ]. - self withCommitDiffs ifTrue: [ - | diffs | - diffs := commits flatCollect: [ :commit | - self importDiffOfCommit: commit ]. - self withFiles ifTrue: [ - self connectDiffs: diffs inside: aGPHRepository ] ] ]. + self withCommitDiffs ifTrue: [ + | diffs | + diffs := commits flatCollect: [ :commit | + self importDiffOfCommit: commit ]. + self withFiles ifTrue: [ + self connectDiffs: diffs inside: aGPHRepository ] ]. ^ commits From bb5c942b0931fe9fc42a67105afb6471f580d0a4 Mon Sep 17 00:00:00 2001 From: Benoit Verhaeghe Date: Fri, 4 Sep 2026 13:53:40 +0200 Subject: [PATCH 53/75] add tests --- ...ModelImporterWithTestResourceTest.class.st | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/src/GitProjectHealth-Model-LocalImporter-Tests/GitLocalModelImporterWithTestResourceTest.class.st b/src/GitProjectHealth-Model-LocalImporter-Tests/GitLocalModelImporterWithTestResourceTest.class.st index 50d68802..0ee5d233 100644 --- a/src/GitProjectHealth-Model-LocalImporter-Tests/GitLocalModelImporterWithTestResourceTest.class.st +++ b/src/GitProjectHealth-Model-LocalImporter-Tests/GitLocalModelImporterWithTestResourceTest.class.st @@ -38,6 +38,33 @@ GitLocalModelImporterWithTestResourceTest >> setUp [ model add: repository. ] +{ #category : #tests } +GitLocalModelImporterWithTestResourceTest >> testConnectionBetweenDivergingBranches [ + + | lastCommitBranchB | + localImporter importRepository: repository. + lastCommitBranchB := (model allWithType: GLHCommit) detect: [ :commit | + commit id + = '505cd202d27777c018af5590a44689928ea55430' ]. + self assert: lastCommitBranchB childCommits size equals: 2 +] + +{ #category : #tests } +GitLocalModelImporterWithTestResourceTest >> testConnectionBetweenSuccessiveBranches [ + + | lastCommitBranchB | + localImporter importRepository: repository. + lastCommitBranchB := (model allWithType: GLHCommit) detect: [ :commit | + commit id + = '02766af59f0b1bbf85861c67aa2128ed5315c8df' ]. + self + assert: lastCommitBranchB parentCommits anyOne id + equals: '4de84ee217a9e6fe1561233db282e5bc661e5e98'. + self + assert: lastCommitBranchB childCommits anyOne id + equals: 'da8ce23a62daec1c558614e2a66aac7f05b9a589' +] + { #category : #tests } GitLocalModelImporterWithTestResourceTest >> testFirstCommitHasADiff [ From fe24813eeb6fb844ccbc84287e877519b47b78a9 Mon Sep 17 00:00:00 2001 From: Benoit Verhaeghe Date: Fri, 4 Sep 2026 13:53:55 +0200 Subject: [PATCH 54/75] update test resource --- res/testResource.zip | Bin 50498 -> 77332 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/res/testResource.zip b/res/testResource.zip index 4937b6409d8ac9054963722d5c1e8d21c3ee79b0..960d33a023420b381af1ddcc92874ef434688044 100644 GIT binary patch delta 18554 zcmb7L30RHU8^8A}mG-pnQ7JBU_a#|EGLn)twA^)U5hgR%gk)Epu_X!Fx3UxwlQ77b zknC9pV`Ld-XZ+85?)~b!E%U!T&-FdN^Pczo-u1lae6Q0>jlR8WY!wq}WbD9#KMyAQ zWn1xGYxd=ir+?Z(GnN?{92sgs58T<8iz;HL7~wgn!p{rUw(%ol*R_*T8<`KsO)Y|^wZ!jVoe$6zv>#WJM{bqcb>3escggzQWmkyo}O5HsXJBQ5w^ZCVAP;}$M@c(Lp#+A_iz;~kHt zjC4bnt=gk6avV`gTT|1ehgSkLP%A3(utkZMPC47#pxrrksHdL=$e82lj%S1sF+ue2 zJe<%j>i{DDHS2@4_;?9htQIGf)zt*``qmD(n*sNRWfN_CGl)Cptad_0b{6`uHrV;- z7SdXIqLnWi*3LBzWYu}c_-St~r)rXtlBkD!<0c&7PW>dvxU$^Ra`xWh$)EUKVURp2 z>4V8oU+T=Geg6hO-sE`k;_yiCH9gK$iSPVj!f{^EYw-94#ia2|C-oaYXwb=oDQ%66 zZhEl0*|=DVUiZwM@nVF<^<^OwgFpRUw`Z$Y)u}#5UtUO?IK?FY(H7OmGw;t_3N4EB zv2FL{g?ZsX*X0hD#}@9NK6U3VS>NOLt2qzL_k5V1?tV%dW3gcKeD;m~(_ZBEOXRyw zl5Y4zIDBHs+x3GFpM7zA>+Y{V+^{c<+S1j3&B@od@2&gQi4_xRYBn^EBdEm=_!0ja z9p%A9wjWBhkI;()h#&BvZVurg;5PIxa$xkOOGEm19&jo0a!mh66yaOS`{lG9=4qqH z7p9+bh_RrjYW-kBTXP(-EY?aqy3#SsqB~~pjO~b@=(eM~5xmcy)CEgdIsgJcR;~Y89tLg!wsdmM9@BCbPX?D z;m4uGGL+@!=z0<_dJ6yG*{CZZ0(3<_7Hk;_U7C*e@(|Z3t zhCoQx0L@&&KX^8-0YtDyl9xFULVlnLb&D8PY58CXn{(|wu$m5F4xlD@HgW8D-yk*cX<2GOgb4u=5C9>~hn7qwZ;%XzTa}EhLJ!+J62X{k_UaU*5)(G5 zn8kGbgJ+{yKtzj$2>}jGNW(d!Q;-_*mJG)<`m z1Q4yh*7b}D0T4|{^PvS(HVOuEw=Nhaqy+;)EuT%OAlCt9nUlFtG5 zHaOwb$Vhudd}EaC?T;R8fQ)R>FrmmJN}GNgc<3Ua3tQ6Q3Si~dU4bWQa0Q(^knK|^ zDrR$KJ<&Y~`QO4y6fFn~VXKQJZLD@eQCfbJ`#+8xGdQgy0}Fp$14!u3<$<-MjYgmbYh?Xlv^ISQ&d(^6tv{4=$`dexq z@W0j5|JYak{S-GF-`735@2|=6FpphEZN^NwY2d2#0;C*MU$utCIgPw z^>XyLz5SBEt!Dhpnvg@&mn<_)OUQh&xXZnQ+9?5DsUMYgXRpn^d6ECswY$C+3r)-( zw3)^EYg`ZUn~$^IcC6T4bz;OUuYlBb$7lR4=sSOSqk%pWewXtKb zC%>6{{)>Kp_SexThh&|*hW=DvZ8c4jcp~k_+DQ+d-t%5@ck+twd-<0xJ(<*L)QGp| za?+eP@^)OkId7Cde@}SqiQ=cX`y{S-xOu}_$MyjVpA?70Hq{G?OZ@uu0MK98B2WxYC_>`t**`EB)!NI#UuiEMT< zfTDkcBQE;g8w1E46?N&SbHj90#P>jJx^^Z0d9JI*m@)7LdHI2#V5WPy5&hjwL|*bk zhchXCP?xYyWME{NdE>yz(H>~=?v7;R1OFHI%ccE)BTDJ7Wwo>aY}1BCmyGa4XJfQz zev7$4Q(q;#*9@6##Go8NUe=B<*HWc_nU8uO2xwTWByJ%+Jsq7KO}5)Z)?=18zVu3n zAS}ivgf#N)s0b%7Cn;>(kj2u!tR^ppj-8-=0jEuEUiL*rl@@4JruGa7*|MdSIi$YTna6Si5;v+n7XKUPC3Pz*48Ek z+;QL<`*0aPPYCtS9Ta?l-N8KkFT)sdqkO=uoIaeVTOQd44nkVK9VWS=g2BK?zb~o0U2ElnsLj<3{vbSQU zLgU4HnqyZ6@~4(twe5vv@1`d|K*REP#?XQi4F;!o&VVffYAmp=Oc~6V$&@^)lq*&% z)IxzkB2`JHD!zm##bPVeGO1jt6!OWgodh)mhzZX_WNI?#u^xd{gAU{|l7RxmvnTD(2DHIy1NGj$j#S*!k&*MwgDv^LEQc6|CYtz76 zVXGuwTf~aS#@n|tY)~cFHazYhheHuV^Ow{Q@>^(|nbh;~2VwuH*tkhQMjJmenRx$E z`G`MKKfdsD+ceW?@sHCk@h`m;jeF$R=i3~``aS;Awa>?_c73R^8ofgpX74k*#7=GU z>@?pJEi*rOBi6-rtI6>{)RW&QrYHBDxoPaOhX>~+e9X-AN_%qeO3>Kg5uv&F4tyN? z$8Yt2#{O{Hm(x9B3D4ub%j-WQ)J4f1d?%M&&&Zo=|IqyfdvrXw%#HVhERSHt7U{VR z9Wq=#R;=MVAt3`gp%tr(icTtduvDqWks#))d3|rkkw~{Kos;>J!l;E(R@i@JilUyJ}?l!M^<9NoA=;E`1&hI5uiZ z2hm{j{R_`8w?-?Uc&-)>uZ-A~_=ibiS^cxR(vtP5xsH>5hFKmeEs4ZJiFz{)0%hR+(;YfmjBJIUT%}sCcK`rug1B9W-BA)brI-xZS^z$Uz;@Xl0s8=|obi@#eu3a(O&8S0IrKR9uZhz*lJGe7-^|7fSdlzEsW^2#K>K zK#2~H0#7!V^<$$*>gFQpZyqKr(QnqvKrV{|L#;(vCXh}4BnNU~K(KKb3W=N=nCXSi zoN7TZmnYOHltPV4C6kH-3OOIUfJ`istGN;tPtH>a1agUj3%0UlW=?h-YdcDu?~A6* zaO}C>g~g5;$E)=CLDtS{#y@#K%<2~8C?5A}L-EtaomtbBUJ4Lt>$b}e6R4p3`YS)G)k}aD`m1fHydBK-XH%u0Z+Gb6d~uN?jqj_2h>gxoBWO>HgR2KN_+8 zjck{KaH$t!JeqIxv>#$nL5YULB7O+4bg_l*f7Q6r4@-^GD{+v8r!AV7;e~D;4Z&5c zDP0SptgnA)sT2z2NrG{9;|nxAY&s0#0$HUpi&26qr=AAK;phSbQG%bI~ z0TgGP0Er@4Dc5K;d>oEKsZ@e1IhC4+D*~L%RWdnWt>!DaYP|cOw718%k$N_7L8Qv! z_5Oc2rOhbVlv}^!pZaCVuj`%J9;~$^efoh&o^|eK%|+5#x)a6{lxQd#xI(7U(59Y~ z5`=y~76P}y4VH|YaYN6NbodHY!D^`xi^r45WoofT%@c`ve1Slv;7j>xkwPgJ^VA|- z{y_MqLHJ(BKX|f_um(VRa5-$e5zjK?;odpsulvlOxT?Eb;I{X+_o{L_TB}|v^74B2 ziJX*Gf9lV=$0f-(j#T1ZOso>AR9vn`E#?cPI5Q|k zN`;Dt>jkMusgjZ_=QPOnP-u{>zmFA5imyoX)4gol#6NQ=ckZ*}*$uYcTiaLNP`+Q* zD$A|UB#Z05E8QD@ec7FZ1KR}t+2_LUr1{fdZf)E4$^d1~5}vC_HS6~^*Y~XJTQ|by zLYY&_b47Ij?iq9J+}AuAvrMt?S&9GfDVN>$K8ZWmwK8hVjpWz&M60$`?o4^rqj2uW z^GB|Qj_vWo-xq()i}Wo0iHb^^Uc)U->Dznn^fK?@zaxIS(e|2o{2I~9*mCx$k#V0u zt5J#?zk^t#`}DL5H;l07V0GZi%V33&J%>?w;lm2PZkid^;MBIWYW18Nk^(ue$b-dl zC5~Jvk1H19tyQ4J|5mERN|8X!#RoW?x^S(ofb^8aOi!&A^KTcPKe2ApvG4DWc=h}K zp2aoWr#{>G_f5XLN#?5g-AFNisL!&D=EXdnrUXzK8H}k(nrbK_1d?F2LMm1YaBayI z%6S5fK&jLS)B?UxB^3*$YB6kCd@;FMx78~mT1rw?(j7-To80-r>cA&2i!L`yM~?k# zz6EQZhxc4CWkL^SeDfqln-W980b($iR?7#>MWoSivL#^Ey~xOE4L1|y#eyYr*g)JVmAe&HBSIUKEshU zt0m||f>?!*&O)g~CD+I#GBrL~;!Q>%6v>oou~?zzE5ss&T&go!A{1EQ@bGNa7q2`h zOnAO`)voM3wByx@y?;%Lys>8bk5ZG2ktcs9uISrq(ONNqg86ch2p8uPnNTDUNU=~ni9&(19j*ho0u>*7rW{`zkiw5CSxbdqpGhSV z^?(2TF8^j4e@W=tlIU)!BZj!K7KC_vfU)G=qJ5h?BP|k!f{)4ugJ~)fu8o2a_e2Q3 z+HTx1Yi8?}K7=N!T%TsuTqaFJn5t#LA~n7%P)p>v{!_{LQm$H|Q7OeDo`kEwAZPn(*z2l!{oqM~k=Gs5LY0;-@cDx9gbHibDzcBl{QeyKC`RtPBHm9Q% zBMM4198=S1ZKw~&pVX@l4-J^tLBEg$O=h0b*V3r@@0nSeX!8An#w;GX@PyuYN=8Ga zik@biV~)}FS|_PGSR~-8RdT5i2NGAr)!;o=$d|}fa+y#n(rAQSDNiJl6BVaHfdn6i z;K?p#4I&$B*rZAy+wkL09S$9Ed)Uc1Yj@zq_GR900#-U~7`t@Zzgzm3hm}r#TgOR` zpS)4Juyj8wV@zS~#Y?x&y6jFm_ie0PcKHjJ2a1O?_%?$lZanduT2u9oyD;XZ!?u~5 z_Da8A-f7ChZ(jGI~;O!*xQ=2aL~zMnB-aGaxgef^-RX#~MmVEt8f*e5q+Ej$GSw3b)-<=v~tnQuH_GZudUx)u#8$Pyp$kj{jkFRE} ze-?E-?464@2%Z(1n9~Uh4ZlWAIQsRPdxBAM7cb;<#)`QpiFH_%hKegZy;%)VO|($q z4l@z<8T!|z;sQZhbn4ExkuDL1uX@g3Vs)*5MUS)JEHv$#Ix6=09N%7F{`5BhUEHbP zrNCxDn=aV^l(8h)KwwF_h!1XOi&Trl;9?x@TwI{LmDk>jf9mB6mUwX5gefYyV!Ei) z@wOj-z;{cQ-@SeJt3%l;$H*ng$G$C&zG?q5=+4x3W7~1#Hl^Jwt1lW-5b|&Kg8*fT z)mL%HhWNj4SK}D|OV(|1gd)}*P1`x#s1jYSI(qteZP;ON2kW=TUk*C;Q~UAtN9sI( z8~W4Zqpz;=?-|Y1M7k``9Nj1F+}$&sr@8(9X3VhRKg7J?SKRsKapa*}iu2t9&Ms1P zI5+arRSyemmTv8m%e{5r z?4U0$Kg*fT6Ppx!>|ZqGPn*~3yiX5)2mn35=MpJ^H#!k7j70Xv*{Db`Kf zphSlE5k~;+=oCz@ej63ZFa~B14eq50G>_gPb8;QT>mgkrEi2pF^w|57p4JM}NiQwwZQ2d%6~3J~E=^^Ve*i)DvT3AW$#i)ydnh1c zBH5IKkppgyA`Ojb)bar>zMr|moibsgwJQWI4&TGW8Eo`$w=3>#?BLXf#bz?c@<6vF zid^YV4W>|bjsu##(t*n)(XAifGdT10a7?}x|2$YOFq8bLPv1-t_Onc#1TC}I%OG~l zxnXi6)L~V?|6S0XIVru!2o^L>gmy_gdlYrA3Qe$#FFIiD46&YJ^hd=)9u=j!HP-erRGedoa={>Cp z-(l^Xnz|V6zi(E%Q!{M{%g$@un^_#J!|WUM(PQ7smatE7@NUgIC)c=Br)&xPUmV&q zsmzTUI}C%@x>GCd2(P5IUiQ#U$TTx)HpC=<+7U)oYkiuLfRX>jB$e%u`?}y34IIDD zor<={99+*9ad=5c`;;5hg^F^Es5#iFj}|=6(`)?qcqtxq;;T0o<236Ylr} zAmQHBcfrB6+@CnZT;rfb+-nFpn3NDkaOVRI54ZqB(sJlz51Eb$7KpU$%w2WtaKj<6 zV^RV;5DVB{cP04n*QWm?J=aag5O*Fn)e|rTNr2%$ZkVAbCI5f&4tLk_!>x)f$qNM| z@N4fu_=&arpf+X-3{+ZyXM526#DpJ{67azx1NcRF68%9(qLV$GYMAVRNXu@EC$PiD zR-^1aj(UW^jkZRz7f~O+(-UmF< z(T&#B_km=I&fZRBA1Ela#If(!i7b}o4N2L?l<81!I}wSXFVPG5(E6XXkrSNK7ctZwaSf;n>F%(>pe} z3wl}LMIDn6igjCY4v-Rxya2Kqaa=kS@8f<(tX=48{I5_9kpTys3Gv63J&q*Lco%Z0 zHX+*Wyl@cZ)s1Z1P(97*1sEOkVMa=$D)T9Yy z^x#NevWmq4H!5Oc!z$qihEbKtI_}2EVzeJn27=^xP@^+?iz(Ps z8rbwm@E~Bv7h6+;uNtUl$vp^lZLu5GHkD9A^=o-BnzEZosD^#%g$fSxsGnw%(U|?F z$a^gQk5ah!UVgxu>OYTAB_Hb6P)v19CzRS|yH*bj+6_EC9Z&a!HoB<8?j#>Tg1@^B zYji$v(Kg*_b9|5O*3|a-G`+`>{#42W5@>Fl4u`XH^no1-TE-< zWxw>z88GcLdhnX&XULOKn%SLprzR|d!27J;IY@m_mkuxCu;Wq1X-AagYJu)gm7u8O zJSuN7$pwC2h2qV{-UnydcxKz0z9=h~5K#74wTQ_%K$NKj)2&8IYIn6?QNJHw<$Yt7iOQAK4Oa%1TikY4Q%xK zFLxQdk|6$^zA-@Dw2lC;VD?YxBkr>bBlZk1u%`^pNqU<*(0p8ArcVhv!W-fs@ zvv_7}ls?>9*#I}t;6^RbZ%aifctmIp(eLMUT7iC>vcpG=cxKO&KHwk(9ua709~Mz? zOT`EPevgQP4R4_$3U0~`VvgC+q>uOMx5R@DZ?6IkHw_Xx=B}?M8vdLHDbR3JcHk1s zE+u`$->)Hv4ey@%FQ3R>OAvof*Ar$(YN%g!#kkhjN3F5(EHHXjj-tF^e|y= z&GHIZ#goxdq>p!0KH&X~2B(%QR&e_-HUZqv>Q8ba7TI@3d(e?cq zp(v)r-Us4<*^Q%*pS1<%buzq9>A&J2ax1LN`JC>amf`?14YSilA943Wg4pohA3)qR zIlz4h=zQKbg7|YfXCMyXbab(dLrvXIw0?eO2&q&0?jV}qc3kV_%$^l}8N@qbULZaa z8`iY)Uta$2PFS?_XSBeyIV~>WB~48Te$8w$ z(MNn`4~^KsE)zi9G$GUZJ%4W_V#C=r%kNO3Fq=p85ohfqhz;*(0mMxUO}KynzaFul zApV?Y6wq-C?BbcdA^M2FI6x2^-l_6mh^0jY@#plKV0Pqu^z1l?>Rb$1aGS{l>RmB# zLJ4zVCvrxPd=?0;4qQQ@JFp}$gMCA*0KjZ1j+oy=gY}r~tAG={ zUTZ~5Od#PgJ0SFt%sbr#iS{JgLQ2#7GOLfot+ELc!i3y1Pp;%p^_99c22&Ylbd(}; z9A|b6bkY-U*jb$o8k<$XEn3hJvfKgJXWPzUa|HjVIzbc6&+Gat&iTH{ibRMOle>1s si{}|DHcH(842Rlsf#!vdS4wSpdZO7^;dw)>Ygx4{Ck2c3yb3(>f6VsBh5!Hn delta 6506 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 Z)M#M2RmSDPOJ*c9+=nt4om=q^^?$!qZ Date: Fri, 4 Sep 2026 14:05:39 +0200 Subject: [PATCH 55/75] testSameHeadCommitForTwoBranches is a bug --- ...ocalModelImporterWithTestResourceTest.class.st | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/GitProjectHealth-Model-LocalImporter-Tests/GitLocalModelImporterWithTestResourceTest.class.st b/src/GitProjectHealth-Model-LocalImporter-Tests/GitLocalModelImporterWithTestResourceTest.class.st index 0ee5d233..0b395587 100644 --- a/src/GitProjectHealth-Model-LocalImporter-Tests/GitLocalModelImporterWithTestResourceTest.class.st +++ b/src/GitProjectHealth-Model-LocalImporter-Tests/GitLocalModelImporterWithTestResourceTest.class.st @@ -80,6 +80,7 @@ GitLocalModelImporterWithTestResourceTest >> testHasRenamedField [ | file renameCommit | localImporter importRepository: repository. + self halt. file := (model allWithType: GLHFileBlob) detect: [ :fileBlob | fileBlob path endsWith: 'renamed-hello.st' ]. self assert: file diffs size equals: 3. @@ -111,3 +112,17 @@ GitLocalModelImporterWithTestResourceTest >> testImportFilesOfBranchDoNotImportN ifNone: [ nil ]. self assert: file isNil ] + +{ #category : #tests } +GitLocalModelImporterWithTestResourceTest >> testSameHeadCommitForTwoBranches [ + + | branchA anotherBranchA | + localImporter importRepository: repository. + branchA := (model allWithType: GLHBranch) detect: [ :branch | + branch name = 'brancha' ]. + anotherBranchA := (model allWithType: GLHBranch) detect: [ :branch | + branch name = 'another-branch-a' ]. + self + assert: branchA commits anyOne + identicalTo: anotherBranchA commits anyOne +] From d8cbebe60995be8ab872e245e45d21c1711181bb Mon Sep 17 00:00:00 2001 From: Benoit Verhaeghe Date: Fri, 4 Sep 2026 14:05:48 +0200 Subject: [PATCH 56/75] update resource --- res/testResource.zip | Bin 77332 -> 80628 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/res/testResource.zip b/res/testResource.zip index 960d33a023420b381af1ddcc92874ef434688044..ae6ef78fadde3ebabb859837f784d56f5032e42f 100644 GIT binary patch delta 8382 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 From 5bd1dd0a1b20cfceb271bb34de9e893baf0ce6f2 Mon Sep 17 00:00:00 2001 From: Benoit Verhaeghe Date: Fri, 4 Sep 2026 14:06:44 +0200 Subject: [PATCH 57/75] remove halt --- .../GitLocalModelImporterWithTestResourceTest.class.st | 1 - 1 file changed, 1 deletion(-) diff --git a/src/GitProjectHealth-Model-LocalImporter-Tests/GitLocalModelImporterWithTestResourceTest.class.st b/src/GitProjectHealth-Model-LocalImporter-Tests/GitLocalModelImporterWithTestResourceTest.class.st index 0b395587..2f17151a 100644 --- a/src/GitProjectHealth-Model-LocalImporter-Tests/GitLocalModelImporterWithTestResourceTest.class.st +++ b/src/GitProjectHealth-Model-LocalImporter-Tests/GitLocalModelImporterWithTestResourceTest.class.st @@ -80,7 +80,6 @@ GitLocalModelImporterWithTestResourceTest >> testHasRenamedField [ | file renameCommit | localImporter importRepository: repository. - self halt. file := (model allWithType: GLHFileBlob) detect: [ :fileBlob | fileBlob path endsWith: 'renamed-hello.st' ]. self assert: file diffs size equals: 3. From 17e69dfb35fc36fa30b51f6139d633da725b4325 Mon Sep 17 00:00:00 2001 From: Benoit Verhaeghe Date: Fri, 4 Sep 2026 14:09:45 +0200 Subject: [PATCH 58/75] fix duplicated commit when importing --- .../GitLocalModelImporter.class.st | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/GitProjectHealth-Model-LocalImporter/GitLocalModelImporter.class.st b/src/GitProjectHealth-Model-LocalImporter/GitLocalModelImporter.class.st index 7c55f4c3..20287d39 100644 --- a/src/GitProjectHealth-Model-LocalImporter/GitLocalModelImporter.class.st +++ b/src/GitProjectHealth-Model-LocalImporter/GitLocalModelImporter.class.st @@ -140,6 +140,10 @@ GitLocalModelImporter >> importBranchesOfRepository: aGLHRepository [ GitLocalModelImporter >> importCommit: aLGitCommit [ | commit | + (self glhModel allWithType: GLHCommit) asOrderedCollection + detect: [ :comm | comm id = aLGitCommit name ] + ifFound: [ :comm | ^ comm ]. + commit := self glhModel newCommit id: aLGitCommit name; message: aLGitCommit message; From ae09a9bea24bf740fb8ce0c484556753696a07ec Mon Sep 17 00:00:00 2001 From: Benoit Verhaeghe Date: Mon, 7 Sep 2026 08:48:13 +0200 Subject: [PATCH 59/75] update mock since it has been deprecated --- .../GLPHImporterMock.class.st | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/GitLabHealth-Model-Analysis-Tests/GLPHImporterMock.class.st b/src/GitLabHealth-Model-Analysis-Tests/GLPHImporterMock.class.st index f89adf80..b55b18a8 100644 --- a/src/GitLabHealth-Model-Analysis-Tests/GLPHImporterMock.class.st +++ b/src/GitLabHealth-Model-Analysis-Tests/GLPHImporterMock.class.st @@ -56,8 +56,15 @@ GLPHImporterMock >> glhModel: anObject [ ] { #category : #'import - repositories' } -GLPHImporterMock >> importBranchesOf: aGLHProject [ - ^ aGLHProject repository branches . +GLPHImporterMock >> importBranchesOf: aGLHProject [ + + self deprecated: #'It has been deprecated' +] + +{ #category : #'import - repositories' } +GLPHImporterMock >> importBranchesOfProject: aGLHProject [ + + ^ aGLHProject repository branches ] { #category : #'import - commits' } From 7b08a3a30b41501ce5b76386fa0705ed1d94049f Mon Sep 17 00:00:00 2001 From: Benoit Verhaeghe Date: Mon, 7 Sep 2026 17:14:20 +0200 Subject: [PATCH 60/75] Start infrastructure mixed model importer --- .../BitBucketModelImporter.class.st | 15 --- .../GitlabModelImporter.class.st | 38 ++------ .../GitModelImporter.class.st | 45 ++++++--- .../GitLocalModelImporter.class.st | 70 ++++++++++++-- .../GitModelMixedImporter.class.st | 91 +++++++++++++++++++ .../package.st | 1 + 6 files changed, 194 insertions(+), 66 deletions(-) create mode 100644 src/GitProjectHealth-Model-MixedImporter/GitModelMixedImporter.class.st create mode 100644 src/GitProjectHealth-Model-MixedImporter/package.st diff --git a/src/BitBucketHealth-Model-Importer/BitBucketModelImporter.class.st b/src/BitBucketHealth-Model-Importer/BitBucketModelImporter.class.st index 96e46172..945cecde 100644 --- a/src/BitBucketHealth-Model-Importer/BitBucketModelImporter.class.st +++ b/src/BitBucketHealth-Model-Importer/BitBucketModelImporter.class.st @@ -1,9 +1,6 @@ Class { #name : #BitBucketModelImporter, #superclass : #GitModelImporter, - #instVars : [ - 'withInitialCommits' - ], #category : #'BitBucketHealth-Model-Importer' } @@ -754,15 +751,3 @@ BitBucketModelImporter >> parseUserIntoGLHUser: userDictionnary [ id: (userDictionnary at: #id); username: (userDictionnary at: #name) ] - -{ #category : #accessing } -BitBucketModelImporter >> withInitialCommits [ - - ^ withInitialCommits -] - -{ #category : #accessing } -BitBucketModelImporter >> withInitialCommits: anObject [ - - withInitialCommits := anObject -] diff --git a/src/GitLabHealth-Model-Importer/GitlabModelImporter.class.st b/src/GitLabHealth-Model-Importer/GitlabModelImporter.class.st index da44303f..68b5e2cb 100644 --- a/src/GitLabHealth-Model-Importer/GitlabModelImporter.class.st +++ b/src/GitLabHealth-Model-Importer/GitlabModelImporter.class.st @@ -1,10 +1,6 @@ Class { #name : #GitlabModelImporter, #superclass : #GitModelImporter, - #instVars : [ - 'withInitialCommits', - 'withInitialMergeRequest' - ], #category : #'GitLabHealth-Model-Importer' } @@ -179,28 +175,26 @@ GitlabModelImporter >> completeImportedProject: aGLHProject [ add: aGLHProject unless: self blockOnIdEquality. - self importLatestPipelinesOfProject: importedProject. + self factoryImporter importLatestPipelinesOfProject: importedProject. "aGLHProject creator: (self importUser: aGLHProject creator_id)." - (self importUser: importedProject creator_id) addCreatedProject: - importedProject. + (self factoryImporter importUser: importedProject creator_id) + addCreatedProject: importedProject. + - importedProject repository: GLHRepository new. self glhModel add: importedProject repository. self completeImportsOfRepository: importedProject repository. - + withInitialMergeRequest ifTrue: [ - self + self factoryImporter importMergeRequestsOfProject: importedProject since: DateAndTime today until: DateAndTime now ]. ^ importedProject - - ] { #category : #'import - repositories' } @@ -591,18 +585,6 @@ GitlabModelImporter >> glhApi: anObject [ repoApi := anObject ] -{ #category : #accessing } -GitlabModelImporter >> glhModel [ - - ^ glhModel -] - -{ #category : #accessing } -GitlabModelImporter >> glhModel: anObject [ - - glhModel := anObject -] - { #category : #'import - users' } GitlabModelImporter >> importActiveHumanUsers [ @@ -1570,7 +1552,7 @@ GitlabModelImporter >> importProject: aProjectID [ | result projectResult | ('Import project with id: ' , aProjectID printString) recordInfo. - (glhModel allWithType: GLHProject) + (self glhModel allWithType: GLHProject) detect: [ :project | project id = aProjectID ] ifFound: [ :project | ^ project ]. @@ -2142,9 +2124,3 @@ GitlabModelImporter >> subGroupsOf: aGroupID [ ^ subgroups ] - -{ #category : #accessing } -GitlabModelImporter >> withInitialCommits: aBoolean [ - - withInitialCommits := aBoolean -] diff --git a/src/GitProjectHealth-Model-Importer/GitModelImporter.class.st b/src/GitProjectHealth-Model-Importer/GitModelImporter.class.st index 8fd7c502..663eaca0 100644 --- a/src/GitProjectHealth-Model-Importer/GitModelImporter.class.st +++ b/src/GitProjectHealth-Model-Importer/GitModelImporter.class.st @@ -16,10 +16,10 @@ Class { 'withCommitDiffs', 'generalReader', 'withCommitDiffsChanges', - 'withDiffRanges' - ], - #classVars : [ - 'currentImporter' + 'withDiffRanges', + 'withInitialCommits', + 'withInitialMergeRequest', + 'factoryImporter' ], #category : #'GitProjectHealth-Model-Importer' } @@ -110,7 +110,7 @@ GitModelImporter class >> initialize [ currentImporter := Dictionary new. ] -{ #category : #initialization } +{ #category : #'class initialization' } GitModelImporter class >> reset [ currentImporter := OrderedDictionary new. ] @@ -148,14 +148,12 @@ GitModelImporter >> blockForBranchEquality [ { #category : #equality } GitModelImporter >> blockForDiffEquality [ -^ [ :existing :new | - | sameSize sameDiff sameFile | - sameSize := existing diffString size = new diffString size. + ^ [ :existing :new | + | sameDiff sameFile | sameDiff := existing diffString = new diffString. sameFile := existing new_path = new new_path. - sameSize and: [ sameDiff and: [ sameFile ] ]. - ] + sameDiff and: [ sameFile ] ] ] { #category : #equality } @@ -407,6 +405,18 @@ GitModelImporter >> configureReaderForUser: reader [ ] +{ #category : #accessing } +GitModelImporter >> factoryImporter [ + + ^ factoryImporter +] + +{ #category : #accessing } +GitModelImporter >> factoryImporter: anObject [ + + factoryImporter := anObject +] + { #category : #utils } GitModelImporter >> filterCommitChanges: aCollection [ @@ -469,7 +479,7 @@ GitModelImporter >> importBranchesOf: aGLHProject [ { #category : #'import - branches' } GitModelImporter >> importBranchesOfProject: aGLHProject [ - ^ self subclassResponsibility + ^ self factoryImporter importBranchesOfRepository: aGLHProject repository ] { #category : #'import - branches' } @@ -740,6 +750,7 @@ GitModelImporter >> initialize [ self withFiles: false. self withDiffRanges: false. self withCommitsSince: (Date today - 1 week) asDateAndTime. + self factoryImporter: self. userCatalogue := GLHUserCatalogueV2 new anImporter: self; yourself. @@ -892,3 +903,15 @@ GitModelImporter >> withFiles: aBoolean [ withFiles := aBoolean ] + +{ #category : #accessing } +GitModelImporter >> withInitialCommits [ + + ^ withInitialCommits +] + +{ #category : #accessing } +GitModelImporter >> withInitialCommits: anObject [ + + withInitialCommits := anObject +] diff --git a/src/GitProjectHealth-Model-LocalImporter/GitLocalModelImporter.class.st b/src/GitProjectHealth-Model-LocalImporter/GitLocalModelImporter.class.st index 20287d39..c679f23b 100644 --- a/src/GitProjectHealth-Model-LocalImporter/GitLocalModelImporter.class.st +++ b/src/GitProjectHealth-Model-LocalImporter/GitLocalModelImporter.class.st @@ -87,7 +87,11 @@ GitLocalModelImporter >> createDiffFrom: patch and: delta [ glhDiff new_file: true ]. delta status = LGitDeltaTypeEnum git_delta_deleted ifTrue: [ glhDiff deleted_file: true ]. - [glhDiff diffString: (patch patchString allButFirstLines: 2).] on: Error do: [ self flag: #'some illegal continuation byte for utf-8 encoding can appears here' ]. + [ glhDiff diffString: (patch patchString allButFirstLines: 2) ] + on: Error + do: [ + self flag: + #'some illegal continuation byte for utf-8 encoding can appears here' ]. ^ glhDiff ] @@ -103,12 +107,17 @@ GitLocalModelImporter >> gitRepository [ GitLocalModelImporter >> gitRepositoryOf: aGLHRepository [ ^ aGLHRepository cacheAt: #localImporterGitReference ifAbsentPut: [ - (LGitRepository on: (aGLHRepository - cacheAt: #localImporterReference - ifAbsent: [ - Error signal: 'Local GLHRepository without importer reference' ])) - init; - yourself ] + | val | + val := aGLHRepository + cacheAt: #localImporterReference + ifAbsentPut: [ self tryClone: aGLHRepository project ]. + val ifNil: [ + Error signal: 'Local GLHRepository without importer reference' ]. + + (LGitRepository on: + (aGLHRepository cacheAt: #localImporterReference ifAbsent: [Error signal: 'Local GLHRepository without importer reference'])) + init; + yourself ] ] { #category : #'import - branches' } @@ -143,10 +152,12 @@ GitLocalModelImporter >> importCommit: aLGitCommit [ (self glhModel allWithType: GLHCommit) asOrderedCollection detect: [ :comm | comm id = aLGitCommit name ] ifFound: [ :comm | ^ comm ]. - commit := self glhModel newCommit id: aLGitCommit name; + title: + (aLGitCommit message lines ifEmpty: [ { '' } ]) first; message: aLGitCommit message; + created_at: aLGitCommit time asDateAndTime; committed_date: aLGitCommit time asDateAndTime; committer_email: aLGitCommit author email; committer_name: aLGitCommit author name; @@ -160,10 +171,29 @@ GitLocalModelImporter >> importCommit: aLGitCommit [ ^ commit ] +{ #category : #'import - commits' } +GitLocalModelImporter >> importCommit: aCommitID ofProject: aGLHProject [ + + | gitCommit commit | + gitCommit := LGitCommit + of: (self gitRepositoryOf: aGLHProject repository) + fromHexString: aCommitID. + + commit := self importCommit: gitCommit. + ^ aGLHProject repository commits add: commit keyedBy: [ :el | el id ] +] + { #category : #'import - commits' } GitLocalModelImporter >> importCommitsOfProject: aProject since: sinceDate until: untilDate [ - self importCommitsOfRepository: aProject repository since: sinceDate until: untilDate + | commits | + commits := self + importCommitsOfRepository: aProject repository + since: sinceDate + until: untilDate. + ^ aProject repository commits + addAll: commits + keyedBy: [ :el | el id ] ] { #category : #'import - repositories' } @@ -356,3 +386,25 @@ GitLocalModelImporter >> resolveCreatorOfCommit: aGLHCommit [ ^ aGLHCommit commitCreator ] + +{ #category : #'as yet unclassified' } +GitLocalModelImporter >> tryClone: aGLHProject [ + + | url clonePath gitUrl repo cloneOptions | + aGLHProject ifNil: [ ^ nil ]. + url := aGLHProject web_url ifNil: [ ^ nil ]. + clonePath := (FileSystem workingDirectory / 'msr' / aGLHProject name). + + clonePath exists ifTrue: [ ^ clonePath ]. + clonePath ensureCreateDirectory. + + gitUrl := url , '.git'. + repo := LGitRepository on: clonePath. + + cloneOptions := repo cloneOptionsStructureClass + withCredentialsProvider: + (IceCredentialsProvider defaultForRemoteUrl: url). + + repo clone: gitUrl options: cloneOptions. + ^ clonePath +] diff --git a/src/GitProjectHealth-Model-MixedImporter/GitModelMixedImporter.class.st b/src/GitProjectHealth-Model-MixedImporter/GitModelMixedImporter.class.st new file mode 100644 index 00000000..70399dff --- /dev/null +++ b/src/GitProjectHealth-Model-MixedImporter/GitModelMixedImporter.class.st @@ -0,0 +1,91 @@ +Class { + #name : #GitModelMixedImporter, + #superclass : #GitModelImporter, + #instVars : [ + 'apiImporter', + 'localImporter' + ], + #category : #'GitProjectHealth-Model-MixedImporter' +} + +{ #category : #accessing } +GitModelMixedImporter >> apiImporter [ + + ^ apiImporter +] + +{ #category : #accessing } +GitModelMixedImporter >> apiImporter: anObject [ + + apiImporter := anObject. + apiImporter factoryImporter: self +] + +{ #category : #accessing } +GitModelMixedImporter >> glhModel: anObject [ + + glhModel := anObject. + self apiImporter ifNotNil: [ :importer | importer glhModel: anObject ]. + self localImporter ifNotNil: [ :importer | importer glhModel: anObject ] +] + +{ #category : #'import - branches' } +GitModelMixedImporter >> importBranchesOfProject: aGLHProject [ + + ^ self localImporter importBranchesOfProject: aGLHProject +] + +{ #category : #'import - branches' } +GitModelMixedImporter >> importBranchesOfRepository: aGLHRepository [ + + ^ self localImporter importBranchesOfRepository: aGLHRepository +] + +{ #category : #'import - commits' } +GitModelMixedImporter >> importCommit: aCommitID ofProject: aGLHProject [ + + ^ self localImporter importCommit: aCommitID ofProject: aGLHProject +] + +{ #category : #'import - commits' } +GitModelMixedImporter >> importCommitsOfProject: aGLHProject since: since until: until [ + + ^ self localImporter importCommitsOfProject: aGLHProject since: since until: until +] + +{ #category : #'import - pipelines' } +GitModelMixedImporter >> importLatestPipelinesOfProject: aGLHProject [ + + ^ self apiImporter importLatestPipelinesOfProject: aGLHProject +] + +{ #category : #'import - merge-requests' } +GitModelMixedImporter >> importMergeRequestsOfProject: aGLHProject since: fromDate until: toDate [ + + ^ self apiImporter importMergeRequestsOfProject: aGLHProject since: fromDate until: toDate +] + +{ #category : #'import - projects' } +GitModelMixedImporter >> importProject: id [ + + ^ self apiImporter importProject: id +] + +{ #category : #'import - users' } +GitModelMixedImporter >> importUser: aUserID [ + + ^ self apiImporter importUser: aUserID +] + +{ #category : #accessing } +GitModelMixedImporter >> localImporter [ + + ^ localImporter +] + +{ #category : #accessing } +GitModelMixedImporter >> localImporter: anObject [ + + localImporter := anObject. + localImporter factoryImporter: self +] diff --git a/src/GitProjectHealth-Model-MixedImporter/package.st b/src/GitProjectHealth-Model-MixedImporter/package.st new file mode 100644 index 00000000..fab22fb7 --- /dev/null +++ b/src/GitProjectHealth-Model-MixedImporter/package.st @@ -0,0 +1 @@ +Package { #name : #'GitProjectHealth-Model-MixedImporter' } From a9ca388e482140e7d422ce12ae9023922f0f0479 Mon Sep 17 00:00:00 2001 From: Benoit Verhaeghe Date: Tue, 8 Sep 2026 15:11:02 +0200 Subject: [PATCH 61/75] clone automatically the repo add import redirection fix import commits of project of local importer to return the correct suite of imported tests in the period organize methods --- .../ProjectMetric.class.st | 20 ++--- .../GitlabModelImporter.class.st | 18 +++-- .../GitModelImporter.class.st | 4 +- .../GitLocalModelImporter.class.st | 79 +++++++++++++------ .../LGitRefSpec.extension.st | 18 +++++ .../GitModelMixedImporter.class.st | 40 ++++++++++ 6 files changed, 137 insertions(+), 42 deletions(-) create mode 100644 src/GitProjectHealth-Model-LocalImporter/LGitRefSpec.extension.st diff --git a/src/GitLabHealth-Model-Analysis/ProjectMetric.class.st b/src/GitLabHealth-Model-Analysis/ProjectMetric.class.st index d7367f21..f627e77a 100644 --- a/src/GitLabHealth-Model-Analysis/ProjectMetric.class.st +++ b/src/GitLabHealth-Model-Analysis/ProjectMetric.class.st @@ -52,15 +52,13 @@ ProjectMetric >> loadCommitsSince: since until: until [ until: until. "download commits unless project cache is not empty" - allCommits := project repository - cacheAt: cacheSymbol - ifAbsentPut: [ - | foundCommits | - foundCommits := glhImporter - importCommitsOfProject: project - since: since - until: until. - foundCommits ]. + allCommits := project repository cacheAt: cacheSymbol ifAbsentPut: [ + | foundCommits | + foundCommits := glhImporter + importCommitsOfProject: project + since: since + until: until. + foundCommits ]. allCommits do: [ :commit | glhImporter importCreatorOfCommit: commit ]. glhImporter chainsCommitsFrom: allCommits. @@ -103,7 +101,9 @@ ProjectMetric >> loadMergeRequestsSince: since until: until [ ProjectMetric >> loadProjectCompleteMergeRequestsSince: since until: until [ | mergeRequests | - mergeRequests := self loadProjectMergeRequestsSince: since until: until. + mergeRequests := self + loadProjectMergeRequestsSince: since + until: until. mergeRequests do: [ :mergeRequest | glhImporter importMergeResquestMerger: mergeRequest. diff --git a/src/GitLabHealth-Model-Importer/GitlabModelImporter.class.st b/src/GitLabHealth-Model-Importer/GitlabModelImporter.class.st index 68b5e2cb..0f3766d4 100644 --- a/src/GitLabHealth-Model-Importer/GitlabModelImporter.class.st +++ b/src/GitLabHealth-Model-Importer/GitlabModelImporter.class.st @@ -1190,16 +1190,22 @@ GitlabModelImporter >> importLatestReleaseOfProject: aGLHProject [ ^ foundRelease. ] -{ #category : #'import - commits' } +{ #category : #'import - merge-requests' } GitlabModelImporter >> importMergeRequestCommits: aGLPHEMergeRequest [ | commits result | aGLPHEMergeRequest commits ifNotNil: [ ^ aGLPHEMergeRequest commits ]. - - result := self repoApi mergeRequests commitsOf: aGLPHEMergeRequest iid inProject: aGLPHEMergeRequest project id. - - commits := (result collect: [ :commitsJson | self parseCommitsResult: commitsJson ]) flattened. - commits := commits collect: [ :commit | self importCommit: commit id ofProject: aGLPHEMergeRequest project ]. + + result := self repoApi mergeRequests + commitsOf: aGLPHEMergeRequest iid + inProject: aGLPHEMergeRequest project id. + + commits := (result collect: [ :commitsJson | + self parseCommitsResult: commitsJson ]) flattened. + commits := commits collect: [ :commit | "Could we use factory.. unsure because sometimes commits of squashed MR are only saved on remote" + self + importCommit: commit id + ofProject: aGLPHEMergeRequest project ]. aGLPHEMergeRequest commits: commits. diff --git a/src/GitProjectHealth-Model-Importer/GitModelImporter.class.st b/src/GitProjectHealth-Model-Importer/GitModelImporter.class.st index 663eaca0..433eeb98 100644 --- a/src/GitProjectHealth-Model-Importer/GitModelImporter.class.st +++ b/src/GitProjectHealth-Model-Importer/GitModelImporter.class.st @@ -192,7 +192,7 @@ GitModelImporter >> blockOnNameEquality [ ^ self blockEqualityOn: #name ] -{ #category : #utils } +{ #category : #utilities } GitModelImporter >> chainsCommitsFrom: commitsCollection [ | dic | @@ -612,6 +612,8 @@ GitModelImporter >> importLatestReleaseOfProject: aGLHProject [ { #category : #'import - merge-requests' } GitModelImporter >> importMergeRequestCommits: mergeRequest [ + + self subclassResponsibility ] { #category : #'import - merge-requests' } diff --git a/src/GitProjectHealth-Model-LocalImporter/GitLocalModelImporter.class.st b/src/GitProjectHealth-Model-LocalImporter/GitLocalModelImporter.class.st index c679f23b..ac8f9d8e 100644 --- a/src/GitProjectHealth-Model-LocalImporter/GitLocalModelImporter.class.st +++ b/src/GitProjectHealth-Model-LocalImporter/GitLocalModelImporter.class.st @@ -95,6 +95,31 @@ GitLocalModelImporter >> createDiffFrom: patch and: delta [ ^ glhDiff ] +{ #category : #operation } +GitLocalModelImporter >> fetchMRRemotes: repo [ + + | refspecMR remote | + "But will miss old ones https://stackoverflow.com/a/75139038/5257140" + remote := repo allRemotes detect: [ :remote | + remote remoteName = 'origin' ]. + refspecMR := LGitRefSpec fromString: + '+refs/merge-requests/*/head:refs/remotes/origin/mr/*'. + remote fetchSpec: refspecMR. + remote + fetchWithCredentials: + (IceCredentialsProvider defaultForRemoteUrl: remote url) + refSpec: refspecMR +] + +{ #category : #accessing } +GitLocalModelImporter >> gitCommitOf: aGLHCommit [ + + ^ aGLHCommit cacheAt: #importerOrigin ifAbsentPut: [ + LGitCommit + of: (self gitRepositoryOf: aGLHCommit repository) + fromHexString: aGLHCommit id ] +] + { #category : #'import - commits' } GitLocalModelImporter >> gitRepository [ @@ -148,7 +173,7 @@ GitLocalModelImporter >> importBranchesOfRepository: aGLHRepository [ { #category : #'import - commits' } GitLocalModelImporter >> importCommit: aLGitCommit [ - | commit | + | commit gitDiff stats | (self glhModel allWithType: GLHCommit) asOrderedCollection detect: [ :comm | comm id = aLGitCommit name ] ifFound: [ :comm | ^ comm ]. @@ -167,6 +192,16 @@ GitLocalModelImporter >> importCommit: aLGitCommit [ parent_ids: (aLGitCommit parents collect: [ :parent | parent name ]); yourself. + gitDiff := LGitDiff of: aLGitCommit repository. + gitDiff + diffTree: (LGitCommit + of: aLGitCommit repository + fromHexString: commit parent_ids anyOne) tree + toTree: aLGitCommit tree. + stats := LGitDiffStats of: aLGitCommit repository. + gitDiff diff_get_stats: stats diff: gitDiff. + commit additions: (stats diff_stats_insertions: stats). + commit deletions: (stats diff_stats_deletions: stats). self resolveCreatorOfCommit: commit. ^ commit ] @@ -209,7 +244,7 @@ GitLocalModelImporter >> importCommitsOfRepository: aGLHRepository [ GitLocalModelImporter >> importCommitsOfRepository: aGPHRepository since: sinceDate until: untilDate [ | revwalk commits alreadyExploredCommit | - commits := OrderedCollection new. + commits := Set new. "to stop when already explored" alreadyExploredCommit := aGPHRepository commits collect: [ :commit | commit id ] @@ -217,21 +252,21 @@ GitLocalModelImporter >> importCommitsOfRepository: aGPHRepository since: sinceD (self gitRepositoryOf: aGPHRepository) allBranches select: #isLocal thenDo: [ :branch | - | gphBranch shouldContinue lastBranchCommits | + | gphBranch shouldContinue currentBranchCommit | gphBranch := (glhModel allWithType: GLHBranch) detect: [ :gBranch | branch gitName = gBranch name ] ifNone: [ nil ]. revwalk := LGitRevwalk of: (self gitRepositoryOf: aGPHRepository). revwalk pushReference: branch. - lastBranchCommits := nil. + currentBranchCommit := nil. shouldContinue := true. [ | glhCommit gitCommit | gitCommit := revwalk next. glhCommit := self importCommit: gitCommit. - lastBranchCommits := glhCommit. + currentBranchCommit := glhCommit. commits add: glhCommit. alreadyExploredCommit add: glhCommit id. glhCommit cacheAt: #importerOrigin put: gitCommit. @@ -239,8 +274,8 @@ GitLocalModelImporter >> importCommitsOfRepository: aGPHRepository since: sinceD gphBranch commits ifEmpty: [ gphBranch addCommit: glhCommit ] ] doWhileTrue: [ revwalk hasNext and: [ - lastBranchCommits committed_date > sinceDate and: [ - (lastBranchCommits parent_ids anySatisfy: [ :id | + currentBranchCommit committed_date > sinceDate and: [ + (currentBranchCommit parent_ids anySatisfy: [ :id | alreadyExploredCommit includes: id ]) not ] ] ]. self chainsCommitsFrom: commits ]. @@ -252,30 +287,26 @@ GitLocalModelImporter >> importCommitsOfRepository: aGPHRepository since: sinceD self withFiles ifTrue: [ self connectDiffs: diffs inside: aGPHRepository ] ]. - - ^ commits + ^ (self glhModel allWithType: GLHCommit) select: [ :commit | commit committed_date between: sinceDate and: untilDate ] ] { #category : #'import - diffs' } GitLocalModelImporter >> importDiffOfCommit: commit [ | sourceCommit parentCommit gitDiff | - sourceCommit := commit - cacheAt: #importerOrigin - ifAbsent: [ Error signal: 'missing importer origin' ]. + sourceCommit := self gitCommitOf: commit. gitDiff := LGitDiff of: sourceCommit repository. commit parentCommits ifNotEmpty: [ - parentCommit := commit parentCommits anyOne - cacheAt: #importerOrigin - ifAbsent: [ - Error signal: 'missing importer origin' ]. + parentCommit := self gitCommitOf: commit parentCommits anyOne. gitDiff := gitDiff diffTree: parentCommit tree toTree: sourceCommit tree ] ifEmpty: [ - gitDiff := gitDiff diffTree: FFIExternalObject null toTree: sourceCommit tree ]. + gitDiff := gitDiff + diffTree: FFIExternalObject null + toTree: sourceCommit tree ]. gitDiff diffFindSimilar. commit diffs: ((1 to: gitDiff numberOfDeltas) collect: [ :idx | @@ -283,6 +314,7 @@ GitLocalModelImporter >> importDiffOfCommit: commit [ delta := gitDiff deltas at: idx. patch := gitDiff patchAt: idx. self createDiffFrom: patch and: delta ]). + "changes are added into the model during the import" self withDiffRanges ifTrue: [ commit diffs do: [ :diff | @@ -332,7 +364,7 @@ GitLocalModelImporter >> importLatestCommitsOfProject [ GitLocalModelImporter >> importLatestCommitsOfProject: aGLHProject [ "this importer load based on inner state local git repository" - self importLatestCommitsOfProject + ^ self importLatestCommitsOfProject ] { #category : #'import - repositories' } @@ -387,24 +419,21 @@ GitLocalModelImporter >> resolveCreatorOfCommit: aGLHCommit [ ^ aGLHCommit commitCreator ] -{ #category : #'as yet unclassified' } +{ #category : #operation } GitLocalModelImporter >> tryClone: aGLHProject [ | url clonePath gitUrl repo cloneOptions | aGLHProject ifNil: [ ^ nil ]. url := aGLHProject web_url ifNil: [ ^ nil ]. - clonePath := (FileSystem workingDirectory / 'msr' / aGLHProject name). - + clonePath := FileSystem workingDirectory / 'msr' / aGLHProject name. clonePath exists ifTrue: [ ^ clonePath ]. - clonePath ensureCreateDirectory. - + clonePath ensureCreateDirectory. gitUrl := url , '.git'. repo := LGitRepository on: clonePath. - cloneOptions := repo cloneOptionsStructureClass withCredentialsProvider: (IceCredentialsProvider defaultForRemoteUrl: url). - repo clone: gitUrl options: cloneOptions. + self fetchMRRemotes: repo. ^ clonePath ] diff --git a/src/GitProjectHealth-Model-LocalImporter/LGitRefSpec.extension.st b/src/GitProjectHealth-Model-LocalImporter/LGitRefSpec.extension.st new file mode 100644 index 00000000..ae56ccc0 --- /dev/null +++ b/src/GitProjectHealth-Model-LocalImporter/LGitRefSpec.extension.st @@ -0,0 +1,18 @@ +Extension { #name : #LGitRefSpec } + +{ #category : #'*GitProjectHealth-Model-LocalImporter' } +LGitRefSpec >> initializeFromString: aString [ + + | stream | + + "self assert: ('(\+?\w(\w|\-|\.|/)*)?\:\w(\w|\-|\.|/)*' asRegex matches: aString)." + "this is wrong, for example ''+refs/merge-requests/*/head:refs/remotes/origin/mr/*' should work" + + stream := aString readStream. + [ + (stream peek = $+) ifTrue: [ + self beForced. + stream next ]. + self source: (stream upTo: $:). + self destination: stream upToEnd ] ensure: [ stream close ] +] diff --git a/src/GitProjectHealth-Model-MixedImporter/GitModelMixedImporter.class.st b/src/GitProjectHealth-Model-MixedImporter/GitModelMixedImporter.class.st index 70399dff..c7e55140 100644 --- a/src/GitProjectHealth-Model-MixedImporter/GitModelMixedImporter.class.st +++ b/src/GitProjectHealth-Model-MixedImporter/GitModelMixedImporter.class.st @@ -53,12 +53,30 @@ GitModelMixedImporter >> importCommitsOfProject: aGLHProject since: since until: ^ self localImporter importCommitsOfProject: aGLHProject since: since until: until ] +{ #category : #'import - diffs' } +GitModelMixedImporter >> importDiffOfCommit: aCommit [ + + ^ self localImporter importDiffOfCommit: aCommit +] + { #category : #'import - pipelines' } GitModelMixedImporter >> importLatestPipelinesOfProject: aGLHProject [ ^ self apiImporter importLatestPipelinesOfProject: aGLHProject ] +{ #category : #'import - merge-requests' } +GitModelMixedImporter >> importMergeRequestCommits: mergeRequest [ + + ^ self apiImporter importMergeRequestCommits: mergeRequest +] + +{ #category : #'import - merge-requests' } +GitModelMixedImporter >> importMergeRequestMergeCommits: aGLHMergeRequest [ + + ^ self apiImporter importMergeRequestMergeCommits: aGLHMergeRequest +] + { #category : #'import - merge-requests' } GitModelMixedImporter >> importMergeRequestsOfProject: aGLHProject since: fromDate until: toDate [ @@ -77,6 +95,12 @@ GitModelMixedImporter >> importUser: aUserID [ ^ self apiImporter importUser: aUserID ] +{ #category : #'import - users' } +GitModelMixedImporter >> importUserByUsername: username [ + + ^ self apiImporter importUserByUsername: username +] + { #category : #accessing } GitModelMixedImporter >> localImporter [ @@ -89,3 +113,19 @@ GitModelMixedImporter >> localImporter: anObject [ localImporter := anObject. localImporter factoryImporter: self ] + +{ #category : #accessing } +GitModelMixedImporter >> withCommitDiffs: aBoolean [ + + super withCommitDiffs: aBoolean. + self localImporter withCommitDiffs: aBoolean. + self apiImporter withCommitDiffs: aBoolean. +] + +{ #category : #accessing } +GitModelMixedImporter >> withDiffRanges: aBoolean [ + + super withDiffRanges: aBoolean. + self localImporter ifNotNil: [ :importer | importer withDiffRanges: aBoolean ]. + self apiImporter ifNotNil: [ :importer | importer withDiffRanges: aBoolean ]. +] From 02f12a9b3f54f65c67df12637f437eed3fd37520 Mon Sep 17 00:00:00 2001 From: Benoit Verhaeghe Date: Wed, 9 Sep 2026 08:50:00 +0200 Subject: [PATCH 62/75] work on tags, diffs, notes, import commit selection --- .../Metric.class.st | 3 +- .../GitLocalModelImporter.class.st | 37 ++++++++++--------- .../GitModelMixedImporter.class.st | 16 +++++++- 3 files changed, 36 insertions(+), 20 deletions(-) diff --git a/src/GitLabHealth-Model-Analysis/Metric.class.st b/src/GitLabHealth-Model-Analysis/Metric.class.st index 2ce23f7f..b05182ff 100644 --- a/src/GitLabHealth-Model-Analysis/Metric.class.st +++ b/src/GitLabHealth-Model-Analysis/Metric.class.st @@ -51,8 +51,7 @@ Metric >> foundSuccessorOf: commits andCompleteImportForMax: commitLimit [ visitChildCommits: c childCommits toStoreThemIn: analyzingCommits upto: commitLimit. - analyzingCommits add: c. - ]. + analyzingCommits add: c ]. i := 0. size := analyzingCommits size. diff --git a/src/GitProjectHealth-Model-LocalImporter/GitLocalModelImporter.class.st b/src/GitProjectHealth-Model-LocalImporter/GitLocalModelImporter.class.st index ac8f9d8e..6174f3f0 100644 --- a/src/GitProjectHealth-Model-LocalImporter/GitLocalModelImporter.class.st +++ b/src/GitProjectHealth-Model-LocalImporter/GitLocalModelImporter.class.st @@ -173,7 +173,7 @@ GitLocalModelImporter >> importBranchesOfRepository: aGLHRepository [ { #category : #'import - commits' } GitLocalModelImporter >> importCommit: aLGitCommit [ - | commit gitDiff stats | + | commit gitDiff stats parentTree | (self glhModel allWithType: GLHCommit) asOrderedCollection detect: [ :comm | comm id = aLGitCommit name ] ifFound: [ :comm | ^ comm ]. @@ -193,11 +193,13 @@ GitLocalModelImporter >> importCommit: aLGitCommit [ (aLGitCommit parents collect: [ :parent | parent name ]); yourself. gitDiff := LGitDiff of: aLGitCommit repository. - gitDiff - diffTree: (LGitCommit - of: aLGitCommit repository - fromHexString: commit parent_ids anyOne) tree - toTree: aLGitCommit tree. + parentTree := commit parent_ids + ifNotEmpty: [ :parentIds | + (LGitCommit + of: aLGitCommit repository + fromHexString: parentIds anyOne) tree ] + ifEmpty: [ FFIExternalObject null ]. + gitDiff diffTree: parentTree toTree: aLGitCommit tree. stats := LGitDiffStats of: aLGitCommit repository. gitDiff diff_get_stats: stats diff: gitDiff. commit additions: (stats diff_stats_insertions: stats). @@ -221,14 +223,10 @@ GitLocalModelImporter >> importCommit: aCommitID ofProject: aGLHProject [ { #category : #'import - commits' } GitLocalModelImporter >> importCommitsOfProject: aProject since: sinceDate until: untilDate [ - | commits | - commits := self - importCommitsOfRepository: aProject repository - since: sinceDate - until: untilDate. - ^ aProject repository commits - addAll: commits - keyedBy: [ :el | el id ] + ^ self + importCommitsOfRepository: aProject repository + since: sinceDate + until: untilDate ] { #category : #'import - repositories' } @@ -243,7 +241,7 @@ GitLocalModelImporter >> importCommitsOfRepository: aGLHRepository [ { #category : #'import - commits' } GitLocalModelImporter >> importCommitsOfRepository: aGPHRepository since: sinceDate until: untilDate [ - | revwalk commits alreadyExploredCommit | + | revwalk commits alreadyExploredCommit toReturn | commits := Set new. "to stop when already explored" alreadyExploredCommit := aGPHRepository commits @@ -286,8 +284,13 @@ GitLocalModelImporter >> importCommitsOfRepository: aGPHRepository since: sinceD self importDiffOfCommit: commit ]. self withFiles ifTrue: [ self connectDiffs: diffs inside: aGPHRepository ] ]. - - ^ (self glhModel allWithType: GLHCommit) select: [ :commit | commit committed_date between: sinceDate and: untilDate ] + aGPHRepository commits addAll: commits keyedBy: [ :el | el id ]. + toReturn := self glhModel allWithType: GLHCommit. + sinceDate ifNotNil: [ + toReturn := toReturn select: [ :c | c committed_date > sinceDate ] ]. + untilDate ifNotNil: [ + toReturn := toReturn select: [ :c | c committed_date < untilDate ] ]. + ^ toReturn ] { #category : #'import - diffs' } diff --git a/src/GitProjectHealth-Model-MixedImporter/GitModelMixedImporter.class.st b/src/GitProjectHealth-Model-MixedImporter/GitModelMixedImporter.class.st index c7e55140..0cedc89d 100644 --- a/src/GitProjectHealth-Model-MixedImporter/GitModelMixedImporter.class.st +++ b/src/GitProjectHealth-Model-MixedImporter/GitModelMixedImporter.class.st @@ -56,7 +56,9 @@ GitModelMixedImporter >> importCommitsOfProject: aGLHProject since: since until: { #category : #'import - diffs' } GitModelMixedImporter >> importDiffOfCommit: aCommit [ - ^ self localImporter importDiffOfCommit: aCommit + ^ [ self localImporter importDiffOfCommit: aCommit ] + on: LGit_GIT_ENOTFOUND + do: [ self apiImporter importDiffOfCommit: aCommit ] ] { #category : #'import - pipelines' } @@ -83,12 +85,24 @@ GitModelMixedImporter >> importMergeRequestsOfProject: aGLHProject since: fromDa ^ self apiImporter importMergeRequestsOfProject: aGLHProject since: fromDate until: toDate ] +{ #category : #'import - notes' } +GitModelMixedImporter >> importNotesOfMergeRequest: mergeRequest [ + + ^ self apiImporter importNotesOfMergeRequest: mergeRequest +] + { #category : #'import - projects' } GitModelMixedImporter >> importProject: id [ ^ self apiImporter importProject: id ] +{ #category : #'import - tags' } +GitModelMixedImporter >> importTagsForProject: aGLHProject [ + + ^ self apiImporter importTagsForProject: aGLHProject +] + { #category : #'import - users' } GitModelMixedImporter >> importUser: aUserID [ From b789a131866a20146f1384097aff1e0b2b7e3fd8 Mon Sep 17 00:00:00 2001 From: Benoit Verhaeghe Date: Wed, 9 Sep 2026 09:17:48 +0200 Subject: [PATCH 63/75] we will need to update the metamodel --- src/GitLabHealth-Model-Generator/GLHMetamodelGenerator.class.st | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/GitLabHealth-Model-Generator/GLHMetamodelGenerator.class.st b/src/GitLabHealth-Model-Generator/GLHMetamodelGenerator.class.st index 8c3fb642..02aba235 100644 --- a/src/GitLabHealth-Model-Generator/GLHMetamodelGenerator.class.st +++ b/src/GitLabHealth-Model-Generator/GLHMetamodelGenerator.class.st @@ -685,7 +685,7 @@ GLHMetamodelGenerator >> tagProperties [ { #category : #tags } GLHMetamodelGenerator >> tagRelations [ - (tag property: #commit) - (commit property: #tag). + (tag property: #commit) *- (commit property: #tags). (tag property: #release) - (release property: #tag). (tag property: #repository) *-<> (repository property: #tags). ] From 8859aaa7f8d0a94ce4843487906843f19bfd22d7 Mon Sep 17 00:00:00 2001 From: Benoit Verhaeghe Date: Wed, 9 Sep 2026 09:32:01 +0200 Subject: [PATCH 64/75] a commit can have several tags --- src/GitLabHealth-Model/GLHCommit.class.st | 56 +++++++++++++---------- src/GitLabHealth-Model/GLHIssue.class.st | 2 +- src/GitLabHealth-Model/GLHJob.class.st | 2 +- src/GitLabHealth-Model/GLHTag.class.st | 12 ++--- src/GitLabHealth-Model/GLHUser.class.st | 2 +- 5 files changed, 40 insertions(+), 34 deletions(-) diff --git a/src/GitLabHealth-Model/GLHCommit.class.st b/src/GitLabHealth-Model/GLHCommit.class.st index 13d2c100..73e79ac9 100644 --- a/src/GitLabHealth-Model/GLHCommit.class.st +++ b/src/GitLabHealth-Model/GLHCommit.class.st @@ -21,11 +21,11 @@ a commit attached to a repository |---| | `branch` | `GLHCommit` | `commits` | `GLHBranch` | | | `childCommits` | `GLHCommit` | `parentCommits` | `GLHCommit` | | -| `commitedMergeRequest` | `GLHCommit` | `mergeRequestCommit` | `GLHMergeRequest` | | | `commitedMergeRequest` | `GLHCommit` | `mergedCommit` | `GLHMergeRequest` | | +| `commitedMergeRequest` | `GLHCommit` | `mergeRequestCommit` | `GLHMergeRequest` | | | `parentCommits` | `GLHCommit` | `childCommits` | `GLHCommit` | | | `squashedMergeRequest` | `GLHCommit` | `squashCommit` | `GLHMergeRequest` | | -| `tag` | `GLHCommit` | `commit` | `GLHTag` | | +| `tags` | `GLHCommit` | `commit` | `GLHTag` | | ## Properties @@ -58,31 +58,31 @@ Class { #traits : 'FamixTNamedEntity + GLHTRef', #classTraits : 'FamixTNamedEntity classTrait + GLHTRef classTrait', #instVars : [ - '#parent_ids => FMProperty defaultValue: \'OrderedCollection new\'', - '#id => FMProperty', - '#short_id => FMProperty', - '#title => FMProperty', - '#author_name => FMProperty', + '#additions => FMProperty', '#author_email => FMProperty', + '#author_name => FMProperty', '#authored_date => FMProperty', - '#committer_name => FMProperty', - '#committer_email => FMProperty', + '#branch => FMMany type: #GLHBranch opposite: #commits', + '#childCommits => FMMany type: #GLHCommit opposite: #parentCommits', + '#commitCreator => FMOne type: #GLHUser opposite: #commits', + '#commitedMergeRequest => FMOne type: #GLHMergeRequest opposite: #mergedCommit', '#committed_date => FMProperty', + '#committer_email => FMProperty', + '#committer_name => FMProperty', '#created_at => FMProperty', - '#message => FMProperty', - '#web_url => FMProperty', - '#additions => FMProperty', '#deletions => FMProperty', - '#repository => FMOne type: #GLHRepository opposite: #commits', - '#branch => FMMany type: #GLHBranch opposite: #commits', '#diffs => FMMany type: #GLHDiff opposite: #commit', - '#commitCreator => FMOne type: #GLHUser opposite: #commits', - '#parentCommits => FMMany type: #GLHCommit opposite: #childCommits', - '#childCommits => FMMany type: #GLHCommit opposite: #parentCommits', + '#id => FMProperty', '#jobs => FMMany type: #GLHJob opposite: #commit', - '#tag => FMOne type: #GLHTag opposite: #commit', - '#commitedMergeRequest => FMOne type: #GLHMergeRequest opposite: #mergedCommit', - '#squashedMergeRequest => FMOne type: #GLHMergeRequest opposite: #squashCommit' + '#message => FMProperty', + '#parentCommits => FMMany type: #GLHCommit opposite: #childCommits', + '#parent_ids => FMProperty defaultValue: \'OrderedCollection new\'', + '#repository => FMOne type: #GLHRepository opposite: #commits', + '#short_id => FMProperty', + '#squashedMergeRequest => FMOne type: #GLHMergeRequest opposite: #squashCommit', + '#tags => FMMany type: #GLHTag opposite: #commit', + '#title => FMProperty', + '#web_url => FMProperty' ], #category : #'GitLabHealth-Model-Entities' } @@ -126,6 +126,12 @@ GLHCommit >> addParentCommit: anObject [ ^ self parentCommits add: anObject ] +{ #category : #adding } +GLHCommit >> addTag: anObject [ + + ^ self tags add: anObject +] + { #category : #accessing } GLHCommit >> additions [ @@ -473,19 +479,19 @@ GLHCommit >> squashedMergeRequest: anObject [ ] { #category : #accessing } -GLHCommit >> tag [ - "Relation named: #tag type: #GLHTag opposite: #commit" +GLHCommit >> tags [ + "Relation named: #tags type: #GLHTag opposite: #commit" - ^ tag + ^ tags ] { #category : #accessing } -GLHCommit >> tag: anObject [ +GLHCommit >> tags: anObject [ - tag := anObject + tags value: anObject ] { #category : #accessing } diff --git a/src/GitLabHealth-Model/GLHIssue.class.st b/src/GitLabHealth-Model/GLHIssue.class.st index 2d6fc7bb..80320c00 100644 --- a/src/GitLabHealth-Model/GLHIssue.class.st +++ b/src/GitLabHealth-Model/GLHIssue.class.st @@ -27,8 +27,8 @@ an Issues help collaboration within a team to plan, track, and deliver work | `description` | `String` | nil | | | `due_date` | `Object` | nil | | | `id` | `Number` | nil | | -| `name` | `String` | nil | Basic name of the entity, not full reference.| | `name` | `String` | nil | | +| `name` | `String` | nil | Basic name of the entity, not full reference.| | `state` | `String` | nil | | | `updated_at` | `Object` | nil | | diff --git a/src/GitLabHealth-Model/GLHJob.class.st b/src/GitLabHealth-Model/GLHJob.class.st index b50f9f3d..bed62ed5 100644 --- a/src/GitLabHealth-Model/GLHJob.class.st +++ b/src/GitLabHealth-Model/GLHJob.class.st @@ -20,8 +20,8 @@ A CI Job | `allow_failure` | `Boolean` | nil | | | `duration` | `Object` | nil | | | `id` | `Number` | nil | | -| `name` | `String` | nil | | | `name` | `String` | nil | Basic name of the entity, not full reference.| +| `name` | `String` | nil | | | `ref` | `String` | nil | | | `web_url` | `String` | nil | | diff --git a/src/GitLabHealth-Model/GLHTag.class.st b/src/GitLabHealth-Model/GLHTag.class.st index 8715d8b0..55558d34 100644 --- a/src/GitLabHealth-Model/GLHTag.class.st +++ b/src/GitLabHealth-Model/GLHTag.class.st @@ -12,7 +12,7 @@ a Tag is a reference to a specific point in the repository's history ### Other | Relation | Origin | Opposite | Type | Comment | |---| -| `commit` | `GLHTag` | `tag` | `GLHCommit` | | +| `commit` | `GLHTag` | `tags` | `GLHCommit` | | | `release` | `GLHTag` | `tag` | `GLHRelease` | | @@ -35,13 +35,13 @@ Class { #traits : 'GLHTRef', #classTraits : 'GLHTRef classTrait', #instVars : [ + '#commit => FMOne type: #GLHCommit opposite: #tags', + '#created_at => FMProperty', '#message => FMProperty', - '#target => FMProperty', '#protected => FMProperty', - '#created_at => FMProperty', - '#commit => FMOne type: #GLHCommit opposite: #tag', '#release => FMOne type: #GLHRelease opposite: #tag', - '#repository => FMOne type: #GLHRepository opposite: #tags' + '#repository => FMOne type: #GLHRepository opposite: #tags', + '#target => FMProperty' ], #category : #'GitLabHealth-Model-Entities' } @@ -57,7 +57,7 @@ GLHTag class >> annotation [ { #category : #accessing } GLHTag >> commit [ - "Relation named: #commit type: #GLHCommit opposite: #tag" + "Relation named: #commit type: #GLHCommit opposite: #tags" ^ commit diff --git a/src/GitLabHealth-Model/GLHUser.class.st b/src/GitLabHealth-Model/GLHUser.class.st index 20a41c87..d6648af4 100644 --- a/src/GitLabHealth-Model/GLHUser.class.st +++ b/src/GitLabHealth-Model/GLHUser.class.st @@ -51,8 +51,8 @@ A GitLab User | `job_title` | `String` | nil | | | `linkedin` | `String` | nil | | | `location` | `String` | nil | | -| `name` | `String` | nil | | | `name` | `String` | nil | Basic name of the entity, not full reference.| +| `name` | `String` | nil | | | `organization` | `String` | nil | | | `pronouns` | `String` | nil | | | `public_email` | `String` | nil | | From 38c294f90f9c32a54a95799f357a31012eb0f232 Mon Sep 17 00:00:00 2001 From: Benoit Verhaeghe Date: Wed, 9 Sep 2026 10:03:50 +0200 Subject: [PATCH 65/75] remove deprecated stuff that no one use anymore --- .../GPModelImporter.class.st | 30 ------------------- .../GitModelImporter.class.st | 28 ----------------- 2 files changed, 58 deletions(-) delete mode 100644 src/GitProjectHealth-Model-Importer/GPModelImporter.class.st diff --git a/src/GitProjectHealth-Model-Importer/GPModelImporter.class.st b/src/GitProjectHealth-Model-Importer/GPModelImporter.class.st deleted file mode 100644 index e3ae04ab..00000000 --- a/src/GitProjectHealth-Model-Importer/GPModelImporter.class.st +++ /dev/null @@ -1,30 +0,0 @@ -" -I am a superclass for Model importer. -I am a try of normalization for the different importer. - -I also define some contract so it is easier to switch from one importer to another -" -Class { - #name : #GPModelImporter, - #superclass : #GitModelImporter, - #classVars : [ - 'currentImporter' - ], - #category : #'GitProjectHealth-Model-Importer' -} - -{ #category : #'accessing - global variables' } -GPModelImporter class >> importers [ - ^ currentImporter -] - -{ #category : #testing } -GPModelImporter class >> isDeprecated [ - - ^ true -] - -{ #category : #initialization } -GPModelImporter class >> reset [ - currentImporter := OrderedDictionary new. -] diff --git a/src/GitProjectHealth-Model-Importer/GitModelImporter.class.st b/src/GitProjectHealth-Model-Importer/GitModelImporter.class.st index 433eeb98..5793eb81 100644 --- a/src/GitProjectHealth-Model-Importer/GitModelImporter.class.st +++ b/src/GitProjectHealth-Model-Importer/GitModelImporter.class.st @@ -100,21 +100,6 @@ GitModelImporter class >> forGitlabWithToken: anApiToken andModel: aGitModel [ ^ self forGitlabHostOn: 'https://gitlab.com/api/v4' withToken: anApiToken andModel: aGitModel ] -{ #category : #'accessing - global variables' } -GitModelImporter class >> importers [ - ^ currentImporter -] - -{ #category : #'class initialization' } -GitModelImporter class >> initialize [ - currentImporter := Dictionary new. -] - -{ #category : #'class initialization' } -GitModelImporter class >> reset [ - currentImporter := OrderedDictionary new. -] - { #category : #initialization } GitModelImporter >> beWithFiles [ @@ -760,19 +745,6 @@ GitModelImporter >> initialize [ self initReader ] -{ #category : #initialization } -GitModelImporter >> makeGlobal [ - ^ self makeGlobal: DateAndTime now printString. -] - -{ #category : #initialization } -GitModelImporter >> makeGlobal: aLabel [ - - currentImporter := GithubModelImporter importers ifNil: [ OrderedDictionary new ]. - currentImporter at: aLabel put: self. - ^ currentImporter -] - { #category : #parsing } GitModelImporter >> parseDiffString: aDiff [ "parse diff string to create entities for each lines" From ee9ada485928f3f27fa5fb4a8a52295fcdaa98e9 Mon Sep 17 00:00:00 2001 From: Benoit Verhaeghe Date: Wed, 9 Sep 2026 14:24:57 +0200 Subject: [PATCH 66/75] kill random failing test --- .../GitLocalModelImporter.class.st | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/GitProjectHealth-Model-LocalImporter/GitLocalModelImporter.class.st b/src/GitProjectHealth-Model-LocalImporter/GitLocalModelImporter.class.st index 6174f3f0..51524202 100644 --- a/src/GitProjectHealth-Model-LocalImporter/GitLocalModelImporter.class.st +++ b/src/GitProjectHealth-Model-LocalImporter/GitLocalModelImporter.class.st @@ -64,7 +64,10 @@ GitLocalModelImporter >> connectDiffs: diffs inside: aGPHRepository [ indexedFiles := (aGPHRepository allToScope: GLHFile) collect: [ :el | el path -> el ] as: Dictionary ]. - diffs do: [ :diff | + "reverse order to have a smart rename history" + (diffs asOrderedCollection sort: [ :diffA :diffB | + diffA commit committed_date > diffB commit committed_date ]) do: [ + :diff | indexedFiles at: diff new_path ifPresent: [ :gphFile | From 4f92918665c24f7740ee7f3911e98091d1198e1b Mon Sep 17 00:00:00 2001 From: Benoit Verhaeghe Date: Wed, 9 Sep 2026 14:44:49 +0200 Subject: [PATCH 67/75] fix importBranchesOfRepository: to work even with not checkouting all branches --- .../GitLocalModelImporter.class.st | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/GitProjectHealth-Model-LocalImporter/GitLocalModelImporter.class.st b/src/GitProjectHealth-Model-LocalImporter/GitLocalModelImporter.class.st index 51524202..424773df 100644 --- a/src/GitProjectHealth-Model-LocalImporter/GitLocalModelImporter.class.st +++ b/src/GitProjectHealth-Model-LocalImporter/GitLocalModelImporter.class.st @@ -154,13 +154,14 @@ GitLocalModelImporter >> importBranchesOfRepository: aGLHRepository [ | branches headCommit | headCommit := (self gitRepositoryOf: aGLHRepository) head object. branches := (self gitRepositoryOf: aGLHRepository) allBranches - select: [ :branch | branch isBranch ] - thenCollect: [ :branch | + collect: [ :branch | GLHBranch new name: branch gitName; - sha: branch targetId hexString; + sha: branch annotatedCommit id hexString; yourself ]. - aGLHRepository branches addAll: branches. + aGLHRepository branches + addAll: branches + keyedBy: [ :el | el name ]. self glhModel addAll: branches keyedBy: [ :el | { el name. From 06870015a5d670e8de9fa1072e80b967a47542ff Mon Sep 17 00:00:00 2001 From: Benoit Verhaeghe Date: Thu, 10 Sep 2026 16:53:09 +0200 Subject: [PATCH 68/75] better retrieve name of remote branches --- .../GitLocalModelImporter.class.st | 12 ++++++------ .../LGitReference.extension.st | 3 ++- .../GitModelMixedImporter.class.st | 6 +++++- 3 files changed, 13 insertions(+), 8 deletions(-) diff --git a/src/GitProjectHealth-Model-LocalImporter/GitLocalModelImporter.class.st b/src/GitProjectHealth-Model-LocalImporter/GitLocalModelImporter.class.st index 424773df..052eb149 100644 --- a/src/GitProjectHealth-Model-LocalImporter/GitLocalModelImporter.class.st +++ b/src/GitProjectHealth-Model-LocalImporter/GitLocalModelImporter.class.st @@ -151,7 +151,7 @@ GitLocalModelImporter >> gitRepositoryOf: aGLHRepository [ { #category : #'import - branches' } GitLocalModelImporter >> importBranchesOfRepository: aGLHRepository [ - | branches headCommit | + | branches headCommit addedBranches | headCommit := (self gitRepositoryOf: aGLHRepository) head object. branches := (self gitRepositoryOf: aGLHRepository) allBranches collect: [ :branch | @@ -159,19 +159,19 @@ GitLocalModelImporter >> importBranchesOfRepository: aGLHRepository [ name: branch gitName; sha: branch annotatedCommit id hexString; yourself ]. - aGLHRepository branches - addAll: branches - keyedBy: [ :el | el name ]. + addedBranches := aGLHRepository branches + addAll: branches + keyedBy: [ :el | el name ]. self glhModel addAll: branches keyedBy: [ :el | { el name. el repository } ]. self withFiles ifTrue: [ - branches do: [ :branch | self importFilesOfBranch: branch ] ]. + addedBranches do: [ :branch | self importFilesOfBranch: branch ] ]. (self gitRepositoryOf: aGLHRepository) resetHardTo: headCommit. - ^ branches + ^ addedBranches ] { #category : #'import - commits' } diff --git a/src/GitProjectHealth-Model-LocalImporter/LGitReference.extension.st b/src/GitProjectHealth-Model-LocalImporter/LGitReference.extension.st index 40b25a3b..32c0d279 100644 --- a/src/GitProjectHealth-Model-LocalImporter/LGitReference.extension.st +++ b/src/GitProjectHealth-Model-LocalImporter/LGitReference.extension.st @@ -5,5 +5,6 @@ LGitReference >> gitName [ ^ self isLocal ifTrue: [ self name withoutPrefix: 'refs/heads/' ] - ifFalse: [ self name withoutPrefix: 'refs/remotes/' ] + ifFalse: [ "removing the refs/remote and the name of the remote" + (self name withoutPrefix: 'refs/remotes/') copyAfter: $/ ] ] diff --git a/src/GitProjectHealth-Model-MixedImporter/GitModelMixedImporter.class.st b/src/GitProjectHealth-Model-MixedImporter/GitModelMixedImporter.class.st index 0cedc89d..d9b90b52 100644 --- a/src/GitProjectHealth-Model-MixedImporter/GitModelMixedImporter.class.st +++ b/src/GitProjectHealth-Model-MixedImporter/GitModelMixedImporter.class.st @@ -44,7 +44,11 @@ GitModelMixedImporter >> importBranchesOfRepository: aGLHRepository [ { #category : #'import - commits' } GitModelMixedImporter >> importCommit: aCommitID ofProject: aGLHProject [ - ^ self localImporter importCommit: aCommitID ofProject: aGLHProject + ^ [ + self localImporter importCommit: aCommitID ofProject: aGLHProject ] + on: LGit_GIT_ENOTFOUND + do: [ + self apiImporter importCommit: aCommitID ofProject: aGLHProject ] ] { #category : #'import - commits' } From c737c8b19f433adf506f37d2e230d433b1021a85 Mon Sep 17 00:00:00 2001 From: Benoit Verhaeghe Date: Fri, 11 Sep 2026 14:00:28 +0200 Subject: [PATCH 69/75] add helper method to explore branches of commit fix file import that uses old API to retrieve branch name --- src/GitLabHealth-Model-Extension/GLHCommit.extension.st | 9 +++++++++ .../GitLocalModelImporter.class.st | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/GitLabHealth-Model-Extension/GLHCommit.extension.st b/src/GitLabHealth-Model-Extension/GLHCommit.extension.st index 8406ee13..d4c3c513 100644 --- a/src/GitLabHealth-Model-Extension/GLHCommit.extension.st +++ b/src/GitLabHealth-Model-Extension/GLHCommit.extension.st @@ -1,5 +1,14 @@ Extension { #name : #GLHCommit } +{ #category : #'*GitLabHealth-Model-Extension' } +GLHCommit >> allBranches [ + "A commit is associated to a branch only if it is the head of its branch. This method search all the branches by exploring the children commit " + + ^ (self + deep: [ :commit | commit childCommits ] + flatCollect: [ :commit | commit branch ]) , { self branch } +] + { #category : #'*GitLabHealth-Model-Extension' } GLHCommit >> computeChurnForMaxCommits: aLimit [ diff --git a/src/GitProjectHealth-Model-LocalImporter/GitLocalModelImporter.class.st b/src/GitProjectHealth-Model-LocalImporter/GitLocalModelImporter.class.st index 052eb149..e29bc627 100644 --- a/src/GitProjectHealth-Model-LocalImporter/GitLocalModelImporter.class.st +++ b/src/GitProjectHealth-Model-LocalImporter/GitLocalModelImporter.class.st @@ -337,7 +337,7 @@ GitLocalModelImporter >> importFilesOfBranch: aGLHBranch [ | gitBranch | gitRepository := self gitRepositoryOf: aGLHBranch repository. gitBranch := gitRepository allBranches detect: [ :branch | - branch shortName = aGLHBranch name ]. + branch gitName = aGLHBranch name ]. gitRepository resetHardTo: gitBranch object. From 8cf19f310f8f0bf28ad94b40001c1c8515a6e0c5 Mon Sep 17 00:00:00 2001 From: Benoit Verhaeghe Date: Fri, 11 Sep 2026 14:02:43 +0200 Subject: [PATCH 70/75] wrong add method call to collection --- src/GitLabHealth-Model-Extension/GLHCommit.extension.st | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/GitLabHealth-Model-Extension/GLHCommit.extension.st b/src/GitLabHealth-Model-Extension/GLHCommit.extension.st index d4c3c513..5fdf4387 100644 --- a/src/GitLabHealth-Model-Extension/GLHCommit.extension.st +++ b/src/GitLabHealth-Model-Extension/GLHCommit.extension.st @@ -6,7 +6,7 @@ GLHCommit >> allBranches [ ^ (self deep: [ :commit | commit childCommits ] - flatCollect: [ :commit | commit branch ]) , { self branch } + flatCollect: [ :commit | commit branch ]) , self branch ] { #category : #'*GitLabHealth-Model-Extension' } From 2be49b5ff66a96f5718590e07eae6b62d1cd4f01 Mon Sep 17 00:00:00 2001 From: Benoit Verhaeghe Date: Tue, 29 Sep 2026 11:14:47 +0200 Subject: [PATCH 71/75] do not import already imported information --- .../GitLocalModelImporter.class.st | 103 +++++++++--------- 1 file changed, 52 insertions(+), 51 deletions(-) diff --git a/src/GitProjectHealth-Model-LocalImporter/GitLocalModelImporter.class.st b/src/GitProjectHealth-Model-LocalImporter/GitLocalModelImporter.class.st index e29bc627..d1661402 100644 --- a/src/GitProjectHealth-Model-LocalImporter/GitLocalModelImporter.class.st +++ b/src/GitProjectHealth-Model-LocalImporter/GitLocalModelImporter.class.st @@ -251,36 +251,32 @@ GitLocalModelImporter >> importCommitsOfRepository: aGPHRepository since: sinceD alreadyExploredCommit := aGPHRepository commits collect: [ :commit | commit id ] as: Set. - (self gitRepositoryOf: aGPHRepository) allBranches - select: #isLocal - thenDo: [ :branch | - | gphBranch shouldContinue currentBranchCommit | - gphBranch := (glhModel allWithType: GLHBranch) - detect: [ :gBranch | branch gitName = gBranch name ] - ifNone: [ nil ]. - - revwalk := LGitRevwalk of: (self gitRepositoryOf: aGPHRepository). - revwalk pushReference: branch. - currentBranchCommit := nil. - shouldContinue := true. - [ - | glhCommit gitCommit | - gitCommit := revwalk next. - glhCommit := self importCommit: gitCommit. - - currentBranchCommit := glhCommit. - commits add: glhCommit. - alreadyExploredCommit add: glhCommit id. - glhCommit cacheAt: #importerOrigin put: gitCommit. - - gphBranch commits ifEmpty: [ gphBranch addCommit: glhCommit ] ] - doWhileTrue: [ - revwalk hasNext and: [ - currentBranchCommit committed_date > sinceDate and: [ - (currentBranchCommit parent_ids anySatisfy: [ :id | - alreadyExploredCommit includes: id ]) not ] ] ]. - - self chainsCommitsFrom: commits ]. + (self gitRepositoryOf: aGPHRepository) allBranches do: [ :branch | + | gphBranch shouldContinue currentBranchCommit | + gphBranch := (glhModel allWithType: GLHBranch) + detect: [ :gBranch | branch gitName = gBranch name ] + ifNone: [ nil ]. + + revwalk := LGitRevwalk of: (self gitRepositoryOf: aGPHRepository). + revwalk pushReference: branch. + currentBranchCommit := nil. + shouldContinue := true. + [ + | glhCommit gitCommit | + gitCommit := revwalk next. + glhCommit := self importCommit: gitCommit. + + currentBranchCommit := glhCommit. + commits add: glhCommit. + alreadyExploredCommit add: glhCommit id. + glhCommit cacheAt: #importerOrigin put: gitCommit. + + gphBranch commits ifEmpty: [ gphBranch addCommit: glhCommit ] ] + doWhileTrue: [ + revwalk hasNext and: [ + currentBranchCommit committed_date > sinceDate ] ]. + + self chainsCommitsFrom: commits ]. self withCommitDiffs ifTrue: [ | diffs | @@ -300,27 +296,32 @@ GitLocalModelImporter >> importCommitsOfRepository: aGPHRepository since: sinceD { #category : #'import - diffs' } GitLocalModelImporter >> importDiffOfCommit: commit [ - | sourceCommit parentCommit gitDiff | - sourceCommit := self gitCommitOf: commit. - gitDiff := LGitDiff of: sourceCommit repository. - - commit parentCommits - ifNotEmpty: [ - parentCommit := self gitCommitOf: commit parentCommits anyOne. - gitDiff := gitDiff - diffTree: parentCommit tree - toTree: sourceCommit tree ] - ifEmpty: [ - gitDiff := gitDiff - diffTree: FFIExternalObject null - toTree: sourceCommit tree ]. - - gitDiff diffFindSimilar. - commit diffs: ((1 to: gitDiff numberOfDeltas) collect: [ :idx | - | delta patch | - delta := gitDiff deltas at: idx. - patch := gitDiff patchAt: idx. - self createDiffFrom: patch and: delta ]). + | sourceCommit gitDiff | + commit diffs ifNotEmpty: [ + sourceCommit := self gitCommitOf: commit. + gitDiff := LGitDiff of: sourceCommit repository. + + commit parent_ids + ifNotEmpty: [ + | parentTree | + parentTree := commit parent_ids + ifNotEmpty: [ :parentIds | + (LGitCommit + of: sourceCommit repository + fromHexString: parentIds anyOne) tree ] + ifEmpty: [ FFIExternalObject null ]. + gitDiff := gitDiff diffTree: parentTree toTree: sourceCommit tree ] + ifEmpty: [ + gitDiff := gitDiff + diffTree: FFIExternalObject null + toTree: sourceCommit tree ]. + + gitDiff diffFindSimilar. + commit diffs: ((1 to: gitDiff numberOfDeltas) collect: [ :idx | + | delta patch | + delta := gitDiff deltas at: idx. + patch := gitDiff patchAt: idx. + self createDiffFrom: patch and: delta ]) ]. "changes are added into the model during the import" self withDiffRanges ifTrue: [ From 164554a0e0f809128a016e7aef6d0df798f15271 Mon Sep 17 00:00:00 2001 From: Benoit Verhaeghe Date: Tue, 29 Sep 2026 13:19:11 +0200 Subject: [PATCH 72/75] add test for local importer (and rename test class) --- ....st => GitLocalModelImporterTest.class.st} | 37 ++++++++++++++----- 1 file changed, 27 insertions(+), 10 deletions(-) rename src/GitProjectHealth-Model-LocalImporter-Tests/{GitLocalModelImporterWithTestResourceTest.class.st => GitLocalModelImporterTest.class.st} (73%) diff --git a/src/GitProjectHealth-Model-LocalImporter-Tests/GitLocalModelImporterWithTestResourceTest.class.st b/src/GitProjectHealth-Model-LocalImporter-Tests/GitLocalModelImporterTest.class.st similarity index 73% rename from src/GitProjectHealth-Model-LocalImporter-Tests/GitLocalModelImporterWithTestResourceTest.class.st rename to src/GitProjectHealth-Model-LocalImporter-Tests/GitLocalModelImporterTest.class.st index 2f17151a..9ab4d790 100644 --- a/src/GitProjectHealth-Model-LocalImporter-Tests/GitLocalModelImporterWithTestResourceTest.class.st +++ b/src/GitProjectHealth-Model-LocalImporter-Tests/GitLocalModelImporterTest.class.st @@ -2,7 +2,7 @@ This tests uses the test repository under GPHBridge (the files of the repository) " Class { - #name : #GitLocalModelImporterWithTestResourceTest, + #name : #GitLocalModelImporterTest, #superclass : #TestCase, #instVars : [ 'localImporter', @@ -13,13 +13,13 @@ Class { } { #category : #accessing } -GitLocalModelImporterWithTestResourceTest class >> resources [ +GitLocalModelImporterTest class >> resources [ ^ Array with: GitLocalModelImporterWithTestResource ] { #category : #running } -GitLocalModelImporterWithTestResourceTest >> setUp [ +GitLocalModelImporterTest >> setUp [ super setUp. localImporter := GitLocalModelImporter new. @@ -39,7 +39,7 @@ GitLocalModelImporterWithTestResourceTest >> setUp [ ] { #category : #tests } -GitLocalModelImporterWithTestResourceTest >> testConnectionBetweenDivergingBranches [ +GitLocalModelImporterTest >> testConnectionBetweenDivergingBranches [ | lastCommitBranchB | localImporter importRepository: repository. @@ -50,7 +50,7 @@ GitLocalModelImporterWithTestResourceTest >> testConnectionBetweenDivergingBranc ] { #category : #tests } -GitLocalModelImporterWithTestResourceTest >> testConnectionBetweenSuccessiveBranches [ +GitLocalModelImporterTest >> testConnectionBetweenSuccessiveBranches [ | lastCommitBranchB | localImporter importRepository: repository. @@ -66,7 +66,7 @@ GitLocalModelImporterWithTestResourceTest >> testConnectionBetweenSuccessiveBran ] { #category : #tests } -GitLocalModelImporterWithTestResourceTest >> testFirstCommitHasADiff [ +GitLocalModelImporterTest >> testFirstCommitHasADiff [ | commit | localImporter importRepository: repository. @@ -76,7 +76,7 @@ GitLocalModelImporterWithTestResourceTest >> testFirstCommitHasADiff [ ] { #category : #tests } -GitLocalModelImporterWithTestResourceTest >> testHasRenamedField [ +GitLocalModelImporterTest >> testHasRenamedField [ | file renameCommit | localImporter importRepository: repository. @@ -90,7 +90,24 @@ GitLocalModelImporterWithTestResourceTest >> testHasRenamedField [ ] { #category : #tests } -GitLocalModelImporterWithTestResourceTest >> testImportFilesOfBranch [ +GitLocalModelImporterTest >> testImportCommitsOfRepositorySinceUntil [ + + | commits originalSize | + localImporter importBranchesOfRepository: repository. + commits := localImporter + importCommitsOfRepository: repository + since: (DateAndTime year: 2026 month: 9 day: 3) + until: (DateAndTime year: 2026 month: 9 day: 5). + originalSize := commits size. + commits := localImporter + importCommitsOfRepository: repository + since: (DateAndTime year: 2026 month: 8 day: 3) + until: (DateAndTime year: 2026 month: 9 day: 5). + self assert: originalSize < commits size +] + +{ #category : #tests } +GitLocalModelImporterTest >> testImportFilesOfBranch [ | file | localImporter importRepository: repository. @@ -100,7 +117,7 @@ GitLocalModelImporterWithTestResourceTest >> testImportFilesOfBranch [ ] { #category : #tests } -GitLocalModelImporterWithTestResourceTest >> testImportFilesOfBranchDoNotImportNotCommitedFile [ +GitLocalModelImporterTest >> testImportFilesOfBranchDoNotImportNotCommitedFile [ | file | (GPHBridge testResources / 'testResource' / 'wrong-file.st') @@ -113,7 +130,7 @@ GitLocalModelImporterWithTestResourceTest >> testImportFilesOfBranchDoNotImportN ] { #category : #tests } -GitLocalModelImporterWithTestResourceTest >> testSameHeadCommitForTwoBranches [ +GitLocalModelImporterTest >> testSameHeadCommitForTwoBranches [ | branchA anotherBranchA | localImporter importRepository: repository. From 910f678eca2bce12af56999b06f66b9bfa3abd31 Mon Sep 17 00:00:00 2001 From: Benoit Verhaeghe Date: Tue, 29 Sep 2026 13:22:40 +0200 Subject: [PATCH 73/75] fix test --- .../GitLocalModelImporter.class.st | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/GitProjectHealth-Model-LocalImporter/GitLocalModelImporter.class.st b/src/GitProjectHealth-Model-LocalImporter/GitLocalModelImporter.class.st index d1661402..af692ada 100644 --- a/src/GitProjectHealth-Model-LocalImporter/GitLocalModelImporter.class.st +++ b/src/GitProjectHealth-Model-LocalImporter/GitLocalModelImporter.class.st @@ -297,7 +297,7 @@ GitLocalModelImporter >> importCommitsOfRepository: aGPHRepository since: sinceD GitLocalModelImporter >> importDiffOfCommit: commit [ | sourceCommit gitDiff | - commit diffs ifNotEmpty: [ + commit diffs ifEmpty: [ sourceCommit := self gitCommitOf: commit. gitDiff := LGitDiff of: sourceCommit repository. From 3ee416f87819aea472368929baebec81540c845a Mon Sep 17 00:00:00 2001 From: Benoit Verhaeghe Date: Thu, 1 Oct 2026 11:25:16 +0200 Subject: [PATCH 74/75] add mixed importer in the baseline --- .../BaselineOfGitLabHealth.class.st | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/BaselineOfGitLabHealth/BaselineOfGitLabHealth.class.st b/src/BaselineOfGitLabHealth/BaselineOfGitLabHealth.class.st index 26d51978..7048a179 100644 --- a/src/BaselineOfGitLabHealth/BaselineOfGitLabHealth.class.st +++ b/src/BaselineOfGitLabHealth/BaselineOfGitLabHealth.class.st @@ -105,7 +105,8 @@ BaselineOfGitLabHealth >> defineGroups: spec [ 'BitBucketHealth-Model-Importer' 'BitBucketHealth-Model-Importer-Tests' 'GitProjectHealth-Model-LocalImporter' - 'GitProjectHealth-Model-LocalImporter-Tests' ). + 'GitProjectHealth-Model-LocalImporter-Tests' + 'GitProjectHealth-Model-MixedImporter' ). spec group: 'default' with: #( 'Core' ) ] @@ -189,16 +190,20 @@ BaselineOfGitLabHealth >> definePackages: spec [ with: [ spec requires: #( 'GitHubHealth-Model-Importer' ) ]. "local" - spec package: 'GitProjectHealth-Model-LocalImporter' with: [ spec requires: #( 'GitProjectHealth-Model-Importer' 'GitLabHealth-Model-Extension' ) ]. + "mixed" + spec + package: 'GitProjectHealth-Model-MixedImporter' + with: [ spec requires: #( 'GitProjectHealth-Model-Importer' ) ]. - spec - package: 'GitProjectHealth-Model-LocalImporter-Tests' - with: [ spec requires: #( 'GitProjectHealth-Model-LocalImporter' 'GitProjectHealth-Bridge' ) ]. + + spec package: 'GitProjectHealth-Model-LocalImporter-Tests' with: [ + spec requires: #( 'GitProjectHealth-Model-LocalImporter' + 'GitProjectHealth-Bridge' ) ]. "model extension" spec From c2a0cb679dce3ca17fa34af2aedfb9bd26c326a3 Mon Sep 17 00:00:00 2001 From: Benoit Verhaeghe Date: Thu, 1 Oct 2026 12:06:11 +0200 Subject: [PATCH 75/75] LICENCE --- LICENSE | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 LICENSE diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..7f02b553 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 Moosetechnology + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE.