couples.go 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174
  1. package hercules
  2. import (
  3. "sort"
  4. "gopkg.in/src-d/go-git.v4"
  5. "gopkg.in/src-d/go-git.v4/plumbing/object"
  6. "gopkg.in/src-d/go-git.v4/utils/merkletrie"
  7. )
  8. type Couples struct {
  9. // The number of developers for which to build the matrix. 0 disables this analysis.
  10. PeopleNumber int
  11. // people store how many times every developer committed to every file.
  12. people []map[string]int
  13. // people_commits is the number of commits each author made
  14. people_commits []int
  15. // files store every file occurred in the same commit with every other file.
  16. files map[string]map[string]int
  17. }
  18. type CouplesResult struct {
  19. PeopleMatrix []map[int]int64
  20. PeopleFiles [][]int
  21. FilesMatrix []map[int]int64
  22. Files []string
  23. }
  24. func (couples *Couples) Name() string {
  25. return "Couples"
  26. }
  27. func (couples *Couples) Provides() []string {
  28. return []string{}
  29. }
  30. func (couples *Couples) Requires() []string {
  31. arr := [...]string{"author", "changes"}
  32. return arr[:]
  33. }
  34. func (couples *Couples) Construct(facts map[string]interface{}) {
  35. if val, exists := facts["PeopleNumber"].(int); exists {
  36. couples.PeopleNumber = val
  37. }
  38. }
  39. func (couples *Couples) Initialize(repository *git.Repository) {
  40. couples.people = make([]map[string]int, couples.PeopleNumber+1)
  41. for i := range couples.people {
  42. couples.people[i] = map[string]int{}
  43. }
  44. couples.people_commits = make([]int, couples.PeopleNumber+1)
  45. couples.files = map[string]map[string]int{}
  46. }
  47. func (couples *Couples) Consume(deps map[string]interface{}) (map[string]interface{}, error) {
  48. author := deps["author"].(int)
  49. if author == MISSING_AUTHOR {
  50. author = couples.PeopleNumber
  51. }
  52. couples.people_commits[author] += 1
  53. tree_diff := deps["changes"].(object.Changes)
  54. context := make([]string, 0)
  55. deleteFile := func(name string) {
  56. // we do not remove the file from people - the context does not expire
  57. delete(couples.files, name)
  58. for _, otherFiles := range couples.files {
  59. delete(otherFiles, name)
  60. }
  61. }
  62. for _, change := range tree_diff {
  63. action, err := change.Action()
  64. if err != nil {
  65. return nil, err
  66. }
  67. toName := change.To.Name
  68. fromName := change.From.Name
  69. switch action {
  70. case merkletrie.Insert:
  71. context = append(context, toName)
  72. couples.people[author][toName] += 1
  73. case merkletrie.Delete:
  74. deleteFile(fromName)
  75. couples.people[author][fromName] += 1
  76. case merkletrie.Modify:
  77. if fromName != toName {
  78. // renamed
  79. couples.files[toName] = couples.files[fromName]
  80. for _, otherFiles := range couples.files {
  81. val, exists := otherFiles[fromName]
  82. if exists {
  83. otherFiles[toName] = val
  84. }
  85. }
  86. deleteFile(fromName)
  87. for _, authorFiles := range couples.people {
  88. val, exists := authorFiles[fromName]
  89. if exists {
  90. authorFiles[toName] = val
  91. delete(authorFiles, fromName)
  92. }
  93. }
  94. }
  95. context = append(context, toName)
  96. couples.people[author][toName] += 1
  97. }
  98. }
  99. for _, file := range context {
  100. for _, otherFile := range context {
  101. lane, exists := couples.files[file]
  102. if !exists {
  103. lane = map[string]int{}
  104. couples.files[file] = lane
  105. }
  106. lane[otherFile] += 1
  107. }
  108. }
  109. return nil, nil
  110. }
  111. func (couples *Couples) Finalize() interface{} {
  112. filesSequence := make([]string, len(couples.files))
  113. i := 0
  114. for file := range couples.files {
  115. filesSequence[i] = file
  116. i++
  117. }
  118. sort.Strings(filesSequence)
  119. filesIndex := map[string]int{}
  120. for i, file := range filesSequence {
  121. filesIndex[file] = i
  122. }
  123. peopleMatrix := make([]map[int]int64, couples.PeopleNumber+1)
  124. peopleFiles := make([][]int, couples.PeopleNumber+1)
  125. for i := range peopleMatrix {
  126. peopleMatrix[i] = map[int]int64{}
  127. for file, commits := range couples.people[i] {
  128. fi, exists := filesIndex[file]
  129. if exists {
  130. peopleFiles[i] = append(peopleFiles[i], fi)
  131. }
  132. for j, otherFiles := range couples.people {
  133. otherCommits := otherFiles[file]
  134. delta := otherCommits
  135. if otherCommits > commits {
  136. delta = commits
  137. }
  138. if delta > 0 {
  139. peopleMatrix[i][j] += int64(delta)
  140. }
  141. }
  142. }
  143. sort.Ints(peopleFiles[i])
  144. }
  145. filesMatrix := make([]map[int]int64, len(filesIndex))
  146. for i := range filesMatrix {
  147. filesMatrix[i] = map[int]int64{}
  148. for otherFile, cooccs := range couples.files[filesSequence[i]] {
  149. filesMatrix[i][filesIndex[otherFile]] = int64(cooccs)
  150. }
  151. }
  152. return CouplesResult{
  153. PeopleMatrix: peopleMatrix, PeopleFiles: peopleFiles,
  154. Files: filesSequence, FilesMatrix: filesMatrix}
  155. }
  156. func init() {
  157. Registry.Register(&Couples{})
  158. }