couples.go 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140
  1. package hercules
  2. import (
  3. "gopkg.in/src-d/go-git.v4"
  4. "gopkg.in/src-d/go-git.v4/plumbing/object"
  5. "gopkg.in/src-d/go-git.v4/utils/merkletrie"
  6. "sort"
  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. // files store every file occurred in the same commit with every other file.
  14. files map[string]map[string]int
  15. }
  16. type CouplesResult struct {
  17. PeopleMatrix []map[int]int64
  18. FilesMatrix []map[int]int
  19. Files []string
  20. }
  21. func (couples *Couples) Name() string {
  22. return "Couples"
  23. }
  24. func (couples *Couples) Provides() []string {
  25. return []string{}
  26. }
  27. func (couples *Couples) Requires() []string {
  28. arr := [...]string{"author", "renamed_changes"}
  29. return arr[:]
  30. }
  31. func (couples *Couples) Initialize(repository *git.Repository) {
  32. couples.people = make([]map[string]int, couples.PeopleNumber)
  33. for i := range couples.people {
  34. couples.people[i] = map[string]int{}
  35. }
  36. couples.files = map[string]map[string]int{}
  37. }
  38. func (couples *Couples) Consume(deps map[string]interface{}) (map[string]interface{}, error) {
  39. author := deps["author"].(int)
  40. tree_diff := deps["renamed_changes"].(object.Changes)
  41. context := make([]string, 0)
  42. deleteFile := func(name string) {
  43. // we do not remove the file from people - the context does not expire
  44. delete(couples.files, name)
  45. for _, otherFiles := range couples.files {
  46. delete(otherFiles, name)
  47. }
  48. }
  49. for _, change := range tree_diff {
  50. action, err := change.Action()
  51. if err != nil {
  52. return nil, err
  53. }
  54. switch action {
  55. case merkletrie.Insert:
  56. context = append(context, change.To.Name)
  57. couples.people[author][change.To.Name] += 1
  58. case merkletrie.Delete:
  59. deleteFile(change.From.Name)
  60. case merkletrie.Modify:
  61. toName := change.To.Name
  62. fromName := change.From.Name
  63. if fromName != toName {
  64. // renamed
  65. couples.files[toName] = couples.files[fromName]
  66. for _, otherFiles := range couples.files {
  67. val, exists := otherFiles[fromName]
  68. if exists {
  69. otherFiles[toName] = val
  70. }
  71. }
  72. deleteFile(change.From.Name)
  73. }
  74. context = append(context, toName)
  75. couples.people[author][toName] += 1
  76. }
  77. }
  78. for _, file := range context {
  79. for _, otherFile := range context {
  80. lane, exists := couples.files[file]
  81. if !exists {
  82. lane = map[string]int{}
  83. couples.files[file] = lane
  84. }
  85. lane[otherFile] += 1
  86. }
  87. }
  88. return nil, nil
  89. }
  90. func (couples *Couples) Finalize() interface{} {
  91. peopleMatrix := make([]map[int]int64, couples.PeopleNumber)
  92. for i := range peopleMatrix {
  93. peopleMatrix[i] = map[int]int64{}
  94. for file, commits := range couples.people[i] {
  95. for j, otherFiles := range couples.people {
  96. if i == j {
  97. continue
  98. }
  99. otherCommits := otherFiles[file]
  100. delta := otherCommits
  101. if otherCommits > commits {
  102. delta = commits
  103. }
  104. if delta > 0 {
  105. peopleMatrix[i][j] += int64(delta)
  106. }
  107. }
  108. }
  109. }
  110. filesSequence := make([]string, len(couples.files))
  111. i := 0
  112. for file := range couples.files {
  113. filesSequence[i] = file
  114. i++
  115. }
  116. sort.Strings(filesSequence)
  117. filesIndex := map[string]int{}
  118. for i, file := range filesSequence {
  119. filesIndex[file] = i
  120. }
  121. filesMatrix := make([]map[int]int, len(filesIndex))
  122. for i := range filesMatrix {
  123. filesMatrix[i] = map[int]int{}
  124. for otherFile, cooccs := range couples.files[filesSequence[i]] {
  125. filesMatrix[i][filesIndex[otherFile]] = cooccs
  126. }
  127. }
  128. return CouplesResult{PeopleMatrix: peopleMatrix, Files: filesSequence, FilesMatrix: filesMatrix}
  129. }