Browse Source

Merge pull request #45 from vmarkovtsev/master

Fix golint warnings
Vadim Markovtsev 7 years ago
parent
commit
aefdedf7ca
23 changed files with 361 additions and 253 deletions
  1. 1 1
      Makefile
  2. 40 35
      blob_cache.go
  3. 62 52
      burndown.go
  4. 1 1
      cmd/hercules/combine.go
  5. 3 3
      cmd/hercules/root.go
  6. 19 14
      couples.go
  7. 3 3
      couples_test.go
  8. 4 0
      day.go
  9. 17 9
      diff.go
  10. 6 1
      diff_refiner.go
  11. 27 25
      file.go
  12. 5 5
      file_test.go
  13. 39 22
      identity.go
  14. 3 3
      identity_test.go
  15. 19 12
      pipeline.go
  16. 1 2
      pipeline_test.go
  17. 1 2
      registry.go
  18. 56 49
      renames.go
  19. 2 2
      renames_test.go
  20. 13 2
      shotness.go
  21. 8 3
      tree_diff.go
  22. 26 4
      uast.go
  23. 5 3
      version.go

+ 1 - 1
Makefile

@@ -30,4 +30,4 @@ ${GOPATH}/src/gopkg.in/bblfsh/client-go.v2:
 .ONESHELL:
 ${GOPATH}/bin/hercules: dependencies *.go cmd/hercules/*.go rbtree/*.go yaml/*.go toposort/*.go pb/*.go
 	cd ${GOPATH}/src/gopkg.in/src-d/hercules.v3
-	go get -ldflags "-X gopkg.in/src-d/hercules.v3.GIT_HASH=$$(git rev-parse HEAD)" gopkg.in/src-d/hercules.v3/cmd/hercules
+	go get -ldflags "-X gopkg.in/src-d/hercules.v3.BinaryGitHash=$$(git rev-parse HEAD)" gopkg.in/src-d/hercules.v3/cmd/hercules

+ 40 - 35
blob_cache.go

@@ -11,9 +11,10 @@ import (
 	"gopkg.in/src-d/go-git.v4/utils/merkletrie"
 )
 
-// This PipelineItem loads the blobs which correspond to the changed files in a commit.
-// It must provide the old and the new objects; "cache" rotates and allows to not load
-// the same blobs twice. Outdated objects are removed so "cache" never grows big.
+// BlobCache loads the blobs which correspond to the changed files in a commit.
+// It is a PipelineItem.
+// It must provide the old and the new objects; "blobCache" rotates and allows to not load
+// the same blobs twice. Outdated objects are removed so "blobCache" never grows big.
 type BlobCache struct {
 	// Specifies how to handle the situation when we encounter a git submodule - an object without
 	// the blob. If false, we look inside .gitmodules and if don't find, raise an error.
@@ -25,47 +26,51 @@ type BlobCache struct {
 }
 
 const (
+	// ConfigBlobCacheIgnoreMissingSubmodules is the name of the configuration option for
+	// BlobCache.Configure() to not check if the referenced submodules exist.
 	ConfigBlobCacheIgnoreMissingSubmodules = "BlobCache.IgnoreMissingSubmodules"
+	// DependencyBlobCache identifies the dependency provided by BlobCache.
 	DependencyBlobCache                    = "blob_cache"
 )
 
-func (cache *BlobCache) Name() string {
+func (blobCache *BlobCache) Name() string {
 	return "BlobCache"
 }
 
-func (cache *BlobCache) Provides() []string {
+func (blobCache *BlobCache) Provides() []string {
 	arr := [...]string{DependencyBlobCache}
 	return arr[:]
 }
 
-func (cache *BlobCache) Requires() []string {
+func (blobCache *BlobCache) Requires() []string {
 	arr := [...]string{DependencyTreeChanges}
 	return arr[:]
 }
 
-func (cache *BlobCache) ListConfigurationOptions() []ConfigurationOption {
+func (blobCache *BlobCache) ListConfigurationOptions() []ConfigurationOption {
 	options := [...]ConfigurationOption{{
 		Name: ConfigBlobCacheIgnoreMissingSubmodules,
-		Description: "Specifies whether to panic if some submodules do not exist and thus " +
-			"the corresponding Git objects cannot be loaded.",
+		Description: "Specifies whether to panic if some referenced submodules do not exist and thus" +
+			" the corresponding Git objects cannot be loaded. Override this if you know that the " +
+				"history is dirty and you want to get things done.",
 		Flag:    "ignore-missing-submodules",
 		Type:    BoolConfigurationOption,
 		Default: false}}
 	return options[:]
 }
 
-func (cache *BlobCache) Configure(facts map[string]interface{}) {
+func (blobCache *BlobCache) Configure(facts map[string]interface{}) {
 	if val, exists := facts[ConfigBlobCacheIgnoreMissingSubmodules].(bool); exists {
-		cache.IgnoreMissingSubmodules = val
+		blobCache.IgnoreMissingSubmodules = val
 	}
 }
 
-func (cache *BlobCache) Initialize(repository *git.Repository) {
-	cache.repository = repository
-	cache.cache = map[plumbing.Hash]*object.Blob{}
+func (blobCache *BlobCache) Initialize(repository *git.Repository) {
+	blobCache.repository = repository
+	blobCache.cache = map[plumbing.Hash]*object.Blob{}
 }
 
-func (self *BlobCache) Consume(deps map[string]interface{}) (map[string]interface{}, error) {
+func (blobCache *BlobCache) Consume(deps map[string]interface{}) (map[string]interface{}, error) {
 	commit := deps["commit"].(*object.Commit)
 	changes := deps[DependencyTreeChanges].(object.Changes)
 	cache := map[plumbing.Hash]*object.Blob{}
@@ -80,7 +85,7 @@ func (self *BlobCache) Consume(deps map[string]interface{}) (map[string]interfac
 		var blob *object.Blob
 		switch action {
 		case merkletrie.Insert:
-			blob, err = self.getBlob(&change.To, commit.File)
+			blob, err = blobCache.getBlob(&change.To, commit.File)
 			if err != nil {
 				fmt.Fprintf(os.Stderr, "file to %s %s\n", change.To.Name, change.To.TreeEntry.Hash)
 			} else {
@@ -88,9 +93,9 @@ func (self *BlobCache) Consume(deps map[string]interface{}) (map[string]interfac
 				newCache[change.To.TreeEntry.Hash] = blob
 			}
 		case merkletrie.Delete:
-			cache[change.From.TreeEntry.Hash], exists = self.cache[change.From.TreeEntry.Hash]
+			cache[change.From.TreeEntry.Hash], exists = blobCache.cache[change.From.TreeEntry.Hash]
 			if !exists {
-				cache[change.From.TreeEntry.Hash], err = self.getBlob(&change.From, commit.File)
+				cache[change.From.TreeEntry.Hash], err = blobCache.getBlob(&change.From, commit.File)
 				if err != nil {
 					if err.Error() != plumbing.ErrObjectNotFound.Error() {
 						fmt.Fprintf(os.Stderr, "file from %s %s\n", change.From.Name,
@@ -102,16 +107,16 @@ func (self *BlobCache) Consume(deps map[string]interface{}) (map[string]interfac
 				}
 			}
 		case merkletrie.Modify:
-			blob, err = self.getBlob(&change.To, commit.File)
+			blob, err = blobCache.getBlob(&change.To, commit.File)
 			if err != nil {
 				fmt.Fprintf(os.Stderr, "file to %s\n", change.To.Name)
 			} else {
 				cache[change.To.TreeEntry.Hash] = blob
 				newCache[change.To.TreeEntry.Hash] = blob
 			}
-			cache[change.From.TreeEntry.Hash], exists = self.cache[change.From.TreeEntry.Hash]
+			cache[change.From.TreeEntry.Hash], exists = blobCache.cache[change.From.TreeEntry.Hash]
 			if !exists {
-				cache[change.From.TreeEntry.Hash], err = self.getBlob(&change.From, commit.File)
+				cache[change.From.TreeEntry.Hash], err = blobCache.getBlob(&change.From, commit.File)
 				if err != nil {
 					fmt.Fprintf(os.Stderr, "file from %s\n", change.From.Name)
 				}
@@ -121,19 +126,19 @@ func (self *BlobCache) Consume(deps map[string]interface{}) (map[string]interfac
 			return nil, err
 		}
 	}
-	self.cache = newCache
+	blobCache.cache = newCache
 	return map[string]interface{}{DependencyBlobCache: cache}, nil
 }
 
-// The definition of a function which loads a git file by the specified path.
+// FileGetter defines a function which loads the Git file by the specified path.
 // The state can be arbitrary though here it always corresponds to the currently processed
 // commit.
 type FileGetter func(path string) (*object.File, error)
 
 // Returns the blob which corresponds to the specified ChangeEntry.
-func (cache *BlobCache) getBlob(entry *object.ChangeEntry, fileGetter FileGetter) (
+func (blobCache *BlobCache) getBlob(entry *object.ChangeEntry, fileGetter FileGetter) (
 	*object.Blob, error) {
-	blob, err := cache.repository.BlobObject(entry.TreeEntry.Hash)
+	blob, err := blobCache.repository.BlobObject(entry.TreeEntry.Hash)
 	if err != nil {
 		if err.Error() != plumbing.ErrObjectNotFound.Error() {
 			fmt.Fprintf(os.Stderr, "getBlob(%s)\n", entry.TreeEntry.Hash.String())
@@ -142,21 +147,21 @@ func (cache *BlobCache) getBlob(entry *object.ChangeEntry, fileGetter FileGetter
 		if entry.TreeEntry.Mode != 0160000 {
 			// this is not a submodule
 			return nil, err
-		} else if cache.IgnoreMissingSubmodules {
+		} else if blobCache.IgnoreMissingSubmodules {
 			return createDummyBlob(entry.TreeEntry.Hash)
 		}
-		file, err_modules := fileGetter(".gitmodules")
-		if err_modules != nil {
-			return nil, err_modules
+		file, errModules := fileGetter(".gitmodules")
+		if errModules != nil {
+			return nil, errModules
 		}
-		contents, err_modules := file.Contents()
-		if err_modules != nil {
-			return nil, err_modules
+		contents, errModules := file.Contents()
+		if errModules != nil {
+			return nil, errModules
 		}
 		modules := config.NewModules()
-		err_modules = modules.Unmarshal([]byte(contents))
-		if err_modules != nil {
-			return nil, err_modules
+		errModules = modules.Unmarshal([]byte(contents))
+		if errModules != nil {
+			return nil, errModules
 		}
 		_, exists := modules.Submodules[entry.Name]
 		if exists {

+ 62 - 52
burndown.go

@@ -19,7 +19,8 @@ import (
 	"gopkg.in/src-d/hercules.v3/yaml"
 )
 
-// BurndownAnalyser allows to gather the line burndown statistics for a Git repository.
+// BurndownAnalysis allows to gather the line burndown statistics for a Git repository.
+// It is a LeafPipelineItem.
 // Reference: https://erikbern.com/2016/12/05/the-half-life-of-code.html
 type BurndownAnalysis struct {
 	// Granularity sets the size of each band - the number of days it spans.
@@ -68,7 +69,8 @@ type BurndownAnalysis struct {
 	reversedPeopleDict []string
 }
 
-// Carries the result of running BurndownAnalysis - it is returned by BurndownAnalysis.Finalize().
+// BurndownResult carries the result of running BurndownAnalysis - it is returned by
+// BurndownAnalysis.Finalize().
 type BurndownResult struct {
 	// [number of samples][number of bands]
 	// The number of samples depends on Sampling: the less Sampling, the bigger the number.
@@ -99,15 +101,22 @@ type BurndownResult struct {
 }
 
 const (
+	// ConfigBurndownGranularity is the name of the option to set BurndownAnalysis.Granularity.
 	ConfigBurndownGranularity = "Burndown.Granularity"
+	// ConfigBurndownSampling is the name of the option to set BurndownAnalysis.Sampling.
 	ConfigBurndownSampling    = "Burndown.Sampling"
-	// Measuring individual files is optional and false by default.
+	// ConfigBurndownTrackFiles enables burndown collection for files.
 	ConfigBurndownTrackFiles = "Burndown.TrackFiles"
-	// Measuring authors is optional and false by default.
+	// ConfigBurndownTrackPeople enables burndown collection for authors.
 	ConfigBurndownTrackPeople = "Burndown.TrackPeople"
-	// Enables some extra debug assertions.
+	// ConfigBurndownDebug enables some extra debug assertions.
 	ConfigBurndownDebug        = "Burndown.Debug"
+	// DefaultBurndownGranularity is the default number of days for BurndownAnalysis.Granularity
+	// and BurndownAnalysis.Sampling.
 	DefaultBurndownGranularity = 30
+	// authorSelf is the internal author index which is used in BurndownAnalysis.Finalize() to
+	// format the author overwrites matrix.
+	authorSelf = (1 << 18) - 2
 )
 
 func (analyser *BurndownAnalysis) Name() string {
@@ -263,9 +272,9 @@ func (analyser *BurndownAnalysis) Finalize() interface{} {
 		mrow := make([]int64, analyser.PeopleNumber+2)
 		peopleMatrix[i] = mrow
 		for key, val := range row {
-			if key == MISSING_AUTHOR {
+			if key == AuthorMissing {
 				key = -1
-			} else if key == SELF_AUTHOR {
+			} else if key == authorSelf {
 				key = -2
 			}
 			mrow[key+2] = val
@@ -772,55 +781,56 @@ func (analyser *BurndownAnalysis) packPersonWithDay(person int, day int) int {
 
 func (analyser *BurndownAnalysis) unpackPersonWithDay(value int) (int, int) {
 	if analyser.PeopleNumber == 0 {
-		return MISSING_AUTHOR, value
+		return AuthorMissing, value
 	}
 	return value >> 14, value & 0x3FFF
 }
 
 func (analyser *BurndownAnalysis) updateStatus(
-	status interface{}, _ int, previous_time_ int, delta int) {
+	status interface{}, _ int, previousValue int, delta int) {
 
-	_, previous_time := analyser.unpackPersonWithDay(previous_time_)
-	status.(map[int]int64)[previous_time] += int64(delta)
+	_, previousTime := analyser.unpackPersonWithDay(previousValue)
+	status.(map[int]int64)[previousTime] += int64(delta)
 }
 
-func (analyser *BurndownAnalysis) updatePeople(people interface{}, _ int, previous_time_ int, delta int) {
-	old_author, previous_time := analyser.unpackPersonWithDay(previous_time_)
-	if old_author == MISSING_AUTHOR {
+func (analyser *BurndownAnalysis) updatePeople(
+	peopleUncasted interface{}, _ int, previousValue int, delta int) {
+	previousAuthor, previousTime := analyser.unpackPersonWithDay(previousValue)
+	if previousAuthor == AuthorMissing {
 		return
 	}
-	casted := people.([]map[int]int64)
-	stats := casted[old_author]
+	people := peopleUncasted.([]map[int]int64)
+	stats := people[previousAuthor]
 	if stats == nil {
 		stats = map[int]int64{}
-		casted[old_author] = stats
+		people[previousAuthor] = stats
 	}
-	stats[previous_time] += int64(delta)
+	stats[previousTime] += int64(delta)
 }
 
 func (analyser *BurndownAnalysis) updateMatrix(
-	matrix_ interface{}, current_time int, previous_time int, delta int) {
+	matrixUncasted interface{}, currentTime int, previousTime int, delta int) {
 
-	matrix := matrix_.([]map[int]int64)
-	new_author, _ := analyser.unpackPersonWithDay(current_time)
-	old_author, _ := analyser.unpackPersonWithDay(previous_time)
-	if old_author == MISSING_AUTHOR {
+	matrix := matrixUncasted.([]map[int]int64)
+	newAuthor, _ := analyser.unpackPersonWithDay(currentTime)
+	oldAuthor, _ := analyser.unpackPersonWithDay(previousTime)
+	if oldAuthor == AuthorMissing {
 		return
 	}
-	if new_author == old_author && delta > 0 {
-		new_author = SELF_AUTHOR
+	if newAuthor == oldAuthor && delta > 0 {
+		newAuthor = authorSelf
 	}
-	row := matrix[old_author]
+	row := matrix[oldAuthor]
 	if row == nil {
 		row = map[int]int64{}
-		matrix[old_author] = row
+		matrix[oldAuthor] = row
 	}
-	cell, exists := row[new_author]
+	cell, exists := row[newAuthor]
 	if !exists {
-		row[new_author] = 0
+		row[newAuthor] = 0
 		cell = 0
 	}
-	row[new_author] = cell + int64(delta)
+	row[newAuthor] = cell + int64(delta)
 }
 
 func (analyser *BurndownAnalysis) newFile(
@@ -852,7 +862,7 @@ func (analyser *BurndownAnalysis) handleInsertion(
 	name := change.To.Name
 	file, exists := analyser.files[name]
 	if exists {
-		return errors.New(fmt.Sprintf("file %s already exists", name))
+		return fmt.Errorf("file %s already exists", name)
 	}
 	file = analyser.newFile(
 		author, analyser.day, lines, analyser.globalStatus, analyser.people, analyser.matrix)
@@ -899,9 +909,9 @@ func (analyser *BurndownAnalysis) handleModification(
 	thisDiffs := diffs[change.To.Name]
 	if file.Len() != thisDiffs.OldLinesOfCode {
 		fmt.Fprintf(os.Stderr, "====TREE====\n%s", file.Dump())
-		return errors.New(fmt.Sprintf("%s: internal integrity error src %d != %d %s -> %s",
+		return fmt.Errorf("%s: internal integrity error src %d != %d %s -> %s",
 			change.To.Name, thisDiffs.OldLinesOfCode, file.Len(),
-			change.From.TreeEntry.Hash.String(), change.To.TreeEntry.Hash.String()))
+			change.From.TreeEntry.Hash.String(), change.To.TreeEntry.Hash.String())
 	}
 
 	// we do not call RunesToDiffLines so the number of lines equals
@@ -923,17 +933,17 @@ func (analyser *BurndownAnalysis) handleModification(
 	}
 
 	for _, edit := range thisDiffs.Diffs {
-		dump_before := ""
+		dumpBefore := ""
 		if analyser.Debug {
-			dump_before = file.Dump()
+			dumpBefore = file.Dump()
 		}
 		length := utf8.RuneCountInString(edit.Text)
-		debug_error := func() {
+		debugError := func() {
 			fmt.Fprintf(os.Stderr, "%s: internal diff error\n", change.To.Name)
 			fmt.Fprintf(os.Stderr, "Update(%d, %d, %d (0), %d (0))\n", analyser.day, position,
 				length, utf8.RuneCountInString(pending.Text))
-			if dump_before != "" {
-				fmt.Fprintf(os.Stderr, "====TREE BEFORE====\n%s====END====\n", dump_before)
+			if dumpBefore != "" {
+				fmt.Fprintf(os.Stderr, "====TREE BEFORE====\n%s====END====\n", dumpBefore)
 			}
 			fmt.Fprintf(os.Stderr, "====TREE AFTER====\n%s====END====\n", file.Dump())
 		}
@@ -947,7 +957,7 @@ func (analyser *BurndownAnalysis) handleModification(
 		case diffmatchpatch.DiffInsert:
 			if pending.Text != "" {
 				if pending.Type == diffmatchpatch.DiffInsert {
-					debug_error()
+					debugError()
 					return errors.New("DiffInsert may not appear after DiffInsert")
 				}
 				file.Update(analyser.packPersonWithDay(author, analyser.day), position, length,
@@ -962,13 +972,13 @@ func (analyser *BurndownAnalysis) handleModification(
 			}
 		case diffmatchpatch.DiffDelete:
 			if pending.Text != "" {
-				debug_error()
+				debugError()
 				return errors.New("DiffDelete may not appear after DiffInsert/DiffDelete")
 			}
 			pending = edit
 		default:
-			debug_error()
-			return errors.New(fmt.Sprintf("diff operation is not supported: %d", edit.Type))
+			debugError()
+			return fmt.Errorf("diff operation is not supported: %d", edit.Type)
 		}
 	}
 	if pending.Text != "" {
@@ -976,8 +986,8 @@ func (analyser *BurndownAnalysis) handleModification(
 		pending.Text = ""
 	}
 	if file.Len() != thisDiffs.NewLinesOfCode {
-		return errors.New(fmt.Sprintf("%s: internal integrity error dst %d != %d",
-			change.To.Name, thisDiffs.NewLinesOfCode, file.Len()))
+		return fmt.Errorf("%s: internal integrity error dst %d != %d",
+			change.To.Name, thisDiffs.NewLinesOfCode, file.Len())
 	}
 	return nil
 }
@@ -985,7 +995,7 @@ func (analyser *BurndownAnalysis) handleModification(
 func (analyser *BurndownAnalysis) handleRename(from, to string) error {
 	file, exists := analyser.files[from]
 	if !exists {
-		return errors.New(fmt.Sprintf("file %s does not exist", from))
+		return fmt.Errorf("file %s does not exist", from)
 	}
 	analyser.files[to] = file
 	delete(analyser.files, from)
@@ -1053,15 +1063,15 @@ func (analyser *BurndownAnalysis) groupStatus() ([]int64, map[string][]int64, []
 }
 
 func (analyser *BurndownAnalysis) updateHistories(
-	globalStatus []int64, file_statuses map[string][]int64, people_statuses [][]int64, delta int) {
+	globalStatus []int64, fileStatuses map[string][]int64, peopleStatuses [][]int64, delta int) {
 	for i := 0; i < delta; i++ {
 		analyser.globalHistory = append(analyser.globalHistory, globalStatus)
 	}
-	to_delete := make([]string, 0)
+	toDelete := make([]string, 0)
 	for key, fh := range analyser.fileHistories {
-		ls, exists := file_statuses[key]
+		ls, exists := fileStatuses[key]
 		if !exists {
-			to_delete = append(to_delete, key)
+			toDelete = append(toDelete, key)
 		} else {
 			for i := 0; i < delta; i++ {
 				fh = append(fh, ls)
@@ -1069,10 +1079,10 @@ func (analyser *BurndownAnalysis) updateHistories(
 			analyser.fileHistories[key] = fh
 		}
 	}
-	for _, key := range to_delete {
+	for _, key := range toDelete {
 		delete(analyser.fileHistories, key)
 	}
-	for key, ls := range file_statuses {
+	for key, ls := range fileStatuses {
 		fh, exists := analyser.fileHistories[key]
 		if exists {
 			continue
@@ -1084,7 +1094,7 @@ func (analyser *BurndownAnalysis) updateHistories(
 	}
 
 	for key, ph := range analyser.peopleHistories {
-		ls := people_statuses[key]
+		ls := peopleStatuses[key]
 		for i := 0; i < delta; i++ {
 			ph = append(ph, ls)
 		}

+ 1 - 1
cmd/hercules/combine.go

@@ -51,7 +51,7 @@ var combineCmd = &cobra.Command{
 		mergedMessage := pb.AnalysisResults{
 			Header: &pb.Metadata{
 				Version:    2,
-				Hash:       hercules.GIT_HASH,
+				Hash:       hercules.BinaryGitHash,
 				Repository: strings.Join(repos, " & "),
 			},
 			Contents: map[string][]byte{},

+ 3 - 3
cmd/hercules/root.go

@@ -235,7 +235,7 @@ func printResults(
 
 	fmt.Println("hercules:")
 	fmt.Println("  version: 3")
-	fmt.Println("  hash:", hercules.GIT_HASH)
+	fmt.Println("  hash:", hercules.BinaryGitHash)
 	fmt.Println("  repository:", uri)
 	fmt.Println("  begin_unix_time:", commonResult.BeginTime)
 	fmt.Println("  end_unix_time:", commonResult.EndTime)
@@ -257,7 +257,7 @@ func protobufResults(
 
 	header := pb.Metadata{
 		Version:    2,
-		Hash:       hercules.GIT_HASH,
+		Hash:       hercules.BinaryGitHash,
 		Repository: uri,
 	}
 	results[nil].(*hercules.CommonAnalysisResult).FillMetadata(&header)
@@ -368,7 +368,7 @@ var versionCmd = &cobra.Command{
 	Long:  ``,
 	Args:  cobra.MaximumNArgs(0),
 	Run: func(cmd *cobra.Command, args []string) {
-		fmt.Printf("Version: %d\nGit:     %s\n", hercules.VERSION, hercules.GIT_HASH)
+		fmt.Printf("Version: %d\nGit:     %s\n", hercules.BinaryVersion, hercules.BinaryGitHash)
 	},
 }
 

+ 19 - 14
couples.go

@@ -13,27 +13,32 @@ import (
 	"gopkg.in/src-d/hercules.v3/yaml"
 )
 
+// CouplesAnalysis calculates the number of common commits for files and authors.
+// The results are matrices, where cell at row X and column Y is the number of commits which
+// changed X and Y together. In case with people, the numbers are summed for every common file.
 type CouplesAnalysis struct {
-	// The number of developers for which to build the matrix. 0 disables this analysis.
+	// PeopleNumber is the number of developers for which to build the matrix. 0 disables this analysis.
 	PeopleNumber int
 
 	// people store how many times every developer committed to every file.
 	people []map[string]int
-	// people_commits is the number of commits each author made
-	people_commits []int
+	// peopleCommits is the number of commits each author made.
+	peopleCommits []int
 	// files store every file occurred in the same commit with every other file.
 	files map[string]map[string]int
-	// references IdentityDetector.ReversedPeopleDict
+	// reversedPeopleDict references IdentityDetector.ReversedPeopleDict
 	reversedPeopleDict []string
 }
 
+// CouplesResult is returned by CouplesAnalysis.Finalize() and carries couples matrices from
+// authors and files.
 type CouplesResult struct {
 	PeopleMatrix []map[int]int64
 	PeopleFiles  [][]int
 	FilesMatrix  []map[int]int64
 	Files        []string
 
-	// references IdentityDetector.ReversedPeopleDict
+	// reversedPeopleDict references IdentityDetector.ReversedPeopleDict
 	reversedPeopleDict []string
 }
 
@@ -70,17 +75,17 @@ func (couples *CouplesAnalysis) Initialize(repository *git.Repository) {
 	for i := range couples.people {
 		couples.people[i] = map[string]int{}
 	}
-	couples.people_commits = make([]int, couples.PeopleNumber+1)
+	couples.peopleCommits = make([]int, couples.PeopleNumber+1)
 	couples.files = map[string]map[string]int{}
 }
 
 func (couples *CouplesAnalysis) Consume(deps map[string]interface{}) (map[string]interface{}, error) {
 	author := deps[DependencyAuthor].(int)
-	if author == MISSING_AUTHOR {
+	if author == AuthorMissing {
 		author = couples.PeopleNumber
 	}
-	couples.people_commits[author] += 1
-	tree_diff := deps[DependencyTreeChanges].(object.Changes)
+	couples.peopleCommits[author]++
+	treeDiff := deps[DependencyTreeChanges].(object.Changes)
 	context := make([]string, 0)
 	deleteFile := func(name string) {
 		// we do not remove the file from people - the context does not expire
@@ -89,7 +94,7 @@ func (couples *CouplesAnalysis) Consume(deps map[string]interface{}) (map[string
 			delete(otherFiles, name)
 		}
 	}
-	for _, change := range tree_diff {
+	for _, change := range treeDiff {
 		action, err := change.Action()
 		if err != nil {
 			return nil, err
@@ -99,10 +104,10 @@ func (couples *CouplesAnalysis) Consume(deps map[string]interface{}) (map[string
 		switch action {
 		case merkletrie.Insert:
 			context = append(context, toName)
-			couples.people[author][toName] += 1
+			couples.people[author][toName]++
 		case merkletrie.Delete:
 			deleteFile(fromName)
-			couples.people[author][fromName] += 1
+			couples.people[author][fromName]++
 		case merkletrie.Modify:
 			if fromName != toName {
 				// renamed
@@ -123,7 +128,7 @@ func (couples *CouplesAnalysis) Consume(deps map[string]interface{}) (map[string
 				}
 			}
 			context = append(context, toName)
-			couples.people[author][toName] += 1
+			couples.people[author][toName]++
 		}
 	}
 	for _, file := range context {
@@ -133,7 +138,7 @@ func (couples *CouplesAnalysis) Consume(deps map[string]interface{}) (map[string
 				lane = map[string]int{}
 				couples.files[file] = lane
 			}
-			lane[otherFile] += 1
+			lane[otherFile]++
 		}
 	}
 	return nil, nil

+ 3 - 3
couples_test.go

@@ -116,9 +116,9 @@ func TestCouplesConsumeFinalize(t *testing.T) {
 	assert.Equal(t, c.files["five"]["five"], 3)
 	assert.Equal(t, c.files["five"]["one"], 1)
 	assert.Equal(t, c.files["five"]["three"], 1)
-	assert.Equal(t, c.people_commits[0], 2)
-	assert.Equal(t, c.people_commits[1], 1)
-	assert.Equal(t, c.people_commits[2], 1)
+	assert.Equal(t, c.peopleCommits[0], 2)
+	assert.Equal(t, c.peopleCommits[1], 1)
+	assert.Equal(t, c.peopleCommits[2], 1)
 	cr := c.Finalize().(CouplesResult)
 	assert.Equal(t, len(cr.Files), 3)
 	assert.Equal(t, cr.Files[0], "five")

+ 4 - 0
day.go

@@ -7,12 +7,16 @@ import (
 	"gopkg.in/src-d/go-git.v4/plumbing/object"
 )
 
+// DaysSinceStart provides the relative date information for every commit.
+// It is a PipelineItem.
 type DaysSinceStart struct {
 	day0        time.Time
 	previousDay int
 }
 
 const (
+	// DependencyDay is the name of the dependency which DaysSinceStart provides - the number
+	// of days since the first commit in the analysed sequence.
 	DependencyDay = "day"
 )
 

+ 17 - 9
diff.go

@@ -14,16 +14,22 @@ import (
 )
 
 // FileDiff calculates the difference of files which were modified.
+// It is a PipelineItem.
 type FileDiff struct {
 	CleanupDisabled bool
 }
 
 const (
+	// ConfigFileDiffDisableCleanup is the name of the configuration option (FileDiff.Configure())
+	// to suppress diffmatchpatch.DiffCleanupSemanticLossless() which is supposed to improve
+	// the human interpretability of diffs.
 	ConfigFileDiffDisableCleanup = "FileDiff.NoCleanup"
 
+	// DependencyFileDiff is the name of the dependency provided by FileDiff.
 	DependencyFileDiff = "file_diff"
 )
 
+// FileDiffData is the type of the dependency provided by FileDiff.
 type FileDiffData struct {
 	OldLinesOfCode int
 	NewLinesOfCode int
@@ -66,28 +72,28 @@ func (diff *FileDiff) Initialize(repository *git.Repository) {}
 func (diff *FileDiff) Consume(deps map[string]interface{}) (map[string]interface{}, error) {
 	result := map[string]FileDiffData{}
 	cache := deps[DependencyBlobCache].(map[plumbing.Hash]*object.Blob)
-	tree_diff := deps[DependencyTreeChanges].(object.Changes)
-	for _, change := range tree_diff {
+	treeDiff := deps[DependencyTreeChanges].(object.Changes)
+	for _, change := range treeDiff {
 		action, err := change.Action()
 		if err != nil {
 			return nil, err
 		}
 		switch action {
 		case merkletrie.Modify:
-			blob_from := cache[change.From.TreeEntry.Hash]
-			blob_to := cache[change.To.TreeEntry.Hash]
+			blobFrom := cache[change.From.TreeEntry.Hash]
+			blobTo := cache[change.To.TreeEntry.Hash]
 			// we are not validating UTF-8 here because for example
 			// git/git 4f7770c87ce3c302e1639a7737a6d2531fe4b160 fetch-pack.c is invalid UTF-8
-			str_from, err := BlobToString(blob_from)
+			strFrom, err := BlobToString(blobFrom)
 			if err != nil {
 				return nil, err
 			}
-			str_to, err := BlobToString(blob_to)
+			strTo, err := BlobToString(blobTo)
 			if err != nil {
 				return nil, err
 			}
 			dmp := diffmatchpatch.New()
-			src, dst, _ := dmp.DiffLinesToRunes(str_from, str_to)
+			src, dst, _ := dmp.DiffLinesToRunes(strFrom, strTo)
 			diffs := dmp.DiffMainRunes(src, dst, false)
 			if !diff.CleanupDisabled {
 				diffs = dmp.DiffCleanupSemanticLossless(diffs)
@@ -104,9 +110,10 @@ func (diff *FileDiff) Consume(deps map[string]interface{}) (map[string]interface
 	return map[string]interface{}{DependencyFileDiff: result}, nil
 }
 
+// CountLines returns the number of lines in a *object.Blob.
 func CountLines(file *object.Blob) (int, error) {
 	if file == nil {
-		return -1, errors.New("Blob is nil: probably not cached.")
+		return -1, errors.New("blob is nil: probably not cached")
 	}
 	reader, err := file.Reader()
 	if err != nil {
@@ -132,9 +139,10 @@ func CountLines(file *object.Blob) (int, error) {
 	return counter, nil
 }
 
+// BlobToString reads *object.Blob and returns its contents as a string.
 func BlobToString(file *object.Blob) (string, error) {
 	if file == nil {
-		return "", errors.New("Blob is nil: probably not cached.")
+		return "", errors.New("blob is nil: probably not cached")
 	}
 	reader, err := file.Reader()
 	if err != nil {

+ 6 - 1
diff_refiner.go

@@ -8,6 +8,10 @@ import (
 	"gopkg.in/src-d/go-git.v4"
 )
 
+// FileDiffRefiner uses UASTs to improve the human interpretability of diffs.
+// It is a PipelineItem.
+// The idea behind this algorithm is simple: in case of multiple choices which are equally
+// optimal, choose the one which touches less AST nodes.
 type FileDiffRefiner struct {
 }
 
@@ -138,7 +142,8 @@ func (ref *FileDiffRefiner) Consume(deps map[string]interface{}) (map[string]int
 	return map[string]interface{}{DependencyFileDiff: result}, nil
 }
 
-// Depth first tree traversal.
+// VisitEachNode is a handy routine to execute a callback on every node in the subtree,
+// including the root itself. Depth first tree traversal.
 func VisitEachNode(root *uast.Node, payload func(*uast.Node)) {
 	queue := []*uast.Node{}
 	queue = append(queue, root)

+ 27 - 25
file.go

@@ -5,13 +5,13 @@ import (
 	"gopkg.in/src-d/hercules.v3/rbtree"
 )
 
-// A status is the something we would like to update during File.Update().
+// Status is the something we would like to keep track of in File.Update().
 type Status struct {
 	data   interface{}
 	update func(interface{}, int, int, int)
 }
 
-// A file encapsulates a balanced binary tree to store line intervals and
+// File encapsulates a balanced binary tree to store line intervals and
 // a cumulative mapping of values to the corresponding length counters. Users
 // are not supposed to create File-s directly; instead, they should call NewFile().
 // NewFileFromTree() is the special constructor which is useful in the tests.
@@ -27,6 +27,8 @@ type File struct {
 	statuses []Status
 }
 
+// NewStatus initializes a new instance of Status struct. It is needed to set the only two
+// private fields which are not supposed to be replaced during the whole lifetime.
 func NewStatus(data interface{}, update func(interface{}, int, int, int)) Status {
 	return Status{data: data, update: update}
 }
@@ -77,9 +79,9 @@ func abs64(v int64) int64 {
 	return v
 }
 
-func (file *File) updateTime(current_time int, previous_time int, delta int) {
+func (file *File) updateTime(currentTime int, previousTime int, delta int) {
 	for _, status := range file.statuses {
-		status.update(status.data, current_time, previous_time, delta)
+		status.update(status.data, currentTime, previousTime, delta)
 	}
 }
 
@@ -146,17 +148,17 @@ func (file *File) Len() int {
 // The code inside this function is probably the most important one throughout
 // the project. It is extensively covered with tests. If you find a bug, please
 // add the corresponding case in file_test.go.
-func (file *File) Update(time int, pos int, ins_length int, del_length int) {
+func (file *File) Update(time int, pos int, insLength int, delLength int) {
 	if time < 0 {
 		panic("time may not be negative")
 	}
 	if pos < 0 {
 		panic("attempt to insert/delete at a negative position")
 	}
-	if ins_length < 0 || del_length < 0 {
-		panic("ins_length and del_length must be nonnegative")
+	if insLength < 0 || delLength < 0 {
+		panic("insLength and delLength must be non-negative")
 	}
-	if ins_length|del_length == 0 {
+	if insLength|delLength == 0 {
 		return
 	}
 	tree := file.tree
@@ -169,19 +171,19 @@ func (file *File) Update(time int, pos int, ins_length int, del_length int) {
 	}
 	iter := tree.FindLE(pos)
 	origin := *iter.Item()
-	file.updateTime(time, time, ins_length)
-	if del_length == 0 {
+	file.updateTime(time, time, insLength)
+	if delLength == 0 {
 		// simple case with insertions only
 		if origin.Key < pos || (origin.Value == time && pos == 0) {
 			iter = iter.Next()
 		}
 		for ; !iter.Limit(); iter = iter.Next() {
-			iter.Item().Key += ins_length
+			iter.Item().Key += insLength
 		}
 		if origin.Value != time {
 			tree.Insert(rbtree.Item{Key: pos, Value: time})
 			if origin.Key < pos {
-				tree.Insert(rbtree.Item{Key: pos + ins_length, Value: origin.Value})
+				tree.Insert(rbtree.Item{Key: pos + insLength, Value: origin.Value})
 			}
 		}
 		return
@@ -190,14 +192,14 @@ func (file *File) Update(time int, pos int, ins_length int, del_length int) {
 	// delete nodes
 	for true {
 		node := iter.Item()
-		next_iter := iter.Next()
-		if next_iter.Limit() {
-			if pos+del_length > node.Key {
+		nextIter := iter.Next()
+		if nextIter.Limit() {
+			if pos+delLength > node.Key {
 				panic("attempt to delete after the end of the file")
 			}
 			break
 		}
-		delta := min(next_iter.Item().Key, pos+del_length) - max(node.Key, pos)
+		delta := min(nextIter.Item().Key, pos+delLength) - max(node.Key, pos)
 		if delta <= 0 {
 			break
 		}
@@ -206,12 +208,12 @@ func (file *File) Update(time int, pos int, ins_length int, del_length int) {
 			origin = *node
 			tree.DeleteWithIterator(iter)
 		}
-		iter = next_iter
+		iter = nextIter
 	}
 
 	// prepare for the keys update
 	var previous *rbtree.Item
-	if ins_length > 0 && (origin.Value != time || origin.Key == pos) {
+	if insLength > 0 && (origin.Value != time || origin.Key == pos) {
 		// insert our new interval
 		if iter.Item().Value == time {
 			prev := iter.Prev()
@@ -232,21 +234,21 @@ func (file *File) Update(time int, pos int, ins_length int, del_length int) {
 	}
 
 	// update the keys of all subsequent nodes
-	delta := ins_length - del_length
+	delta := insLength - delLength
 	if delta != 0 {
 		for iter = iter.Next(); !iter.Limit(); iter = iter.Next() {
 			// we do not need to re-balance the tree
 			iter.Item().Key += delta
 		}
-		// have to adjust origin in case ins_length == 0
+		// have to adjust origin in case insLength == 0
 		if origin.Key > pos {
 			origin.Key += delta
 		}
 	}
 
-	if ins_length > 0 {
+	if insLength > 0 {
 		if origin.Value != time {
-			tree.Insert(rbtree.Item{Key: pos + ins_length, Value: origin.Value})
+			tree.Insert(rbtree.Item{Key: pos + insLength, Value: origin.Value})
 		} else if pos == 0 {
 			// recover the beginning
 			tree.Insert(rbtree.Item{Key: pos, Value: time})
@@ -292,12 +294,12 @@ func (file *File) Validate() {
 	if file.tree.Max().Item().Value != TreeEnd {
 		panic(fmt.Sprintf("the last value in the tree must be %d", TreeEnd))
 	}
-	prev_key := -1
+	prevKey := -1
 	for iter := file.tree.Min(); !iter.Limit(); iter = iter.Next() {
 		node := iter.Item()
-		if node.Key == prev_key {
+		if node.Key == prevKey {
 			panic(fmt.Sprintf("duplicate tree key: %d", node.Key))
 		}
-		prev_key = node.Key
+		prevKey = node.Key
 	}
 }

+ 5 - 5
file_test.go

@@ -8,8 +8,8 @@ import (
 )
 
 func updateStatusFile(
-	status interface{}, _ int, previous_time int, delta int) {
-	status.(map[int]int64)[previous_time] += int64(delta)
+	status interface{}, _ int, previousTime int, delta int) {
+	status.(map[int]int64)[previousTime] += int64(delta)
 }
 
 func fixtureFile() (*File, map[int]int64) {
@@ -44,9 +44,9 @@ func TestBullshitFile(t *testing.T) {
 	testPanicFile(t, func(file *File) { file.Update(1, 110, 10, 0) }, "insert")
 	testPanicFile(t, func(file *File) { file.Update(1, -10, 0, 10) }, "delete")
 	testPanicFile(t, func(file *File) { file.Update(1, 100, 0, 10) }, "delete")
-	testPanicFile(t, func(file *File) { file.Update(1, 0, -10, 0) }, "length")
-	testPanicFile(t, func(file *File) { file.Update(1, 0, 0, -10) }, "length")
-	testPanicFile(t, func(file *File) { file.Update(1, 0, -10, -10) }, "length")
+	testPanicFile(t, func(file *File) { file.Update(1, 0, -10, 0) }, "Length")
+	testPanicFile(t, func(file *File) { file.Update(1, 0, 0, -10) }, "Length")
+	testPanicFile(t, func(file *File) { file.Update(1, 0, -10, -10) }, "Length")
 	testPanicFile(t, func(file *File) { file.Update(-1, 0, 10, 10) }, "time")
 	file, status := fixtureFile()
 	file.Update(1, 10, 0, 0)

+ 39 - 22
identity.go

@@ -10,23 +10,40 @@ import (
 	"gopkg.in/src-d/go-git.v4/plumbing/object"
 )
 
+// IdentityDetector determines the author of a commit. Same person can commit under different
+// signatures, and we apply some heuristics to merge those together.
+// It is a PipelineItem.
 type IdentityDetector struct {
-	// Maps email || name  -> developer id.
+	// PeopleDict maps email || name  -> developer id.
 	PeopleDict map[string]int
-	// Maps developer id -> description
+	// ReversedPeopleDict maps developer id -> description
 	ReversedPeopleDict []string
 }
 
 const (
-	MISSING_AUTHOR   = (1 << 18) - 1
-	SELF_AUTHOR      = (1 << 18) - 2
-	UNMATCHED_AUTHOR = "<unmatched>"
+	// AuthorMissing is the internal author index which denotes any unmatched identities
+	// (IdentityDetector.Consume()).
+	AuthorMissing   = (1 << 18) - 1
+	// AuthorMissingName is the string name which corresponds to AuthorMissing.
+	AuthorMissingName = "<unmatched>"
 
+	// FactIdentityDetectorPeopleDict is the name of the fact which is inserted in
+	// IdentityDetector.Configure(). It corresponds to IdentityDetector.PeopleDict - the mapping
+	// from the signatures to the author indices.
 	FactIdentityDetectorPeopleDict         = "IdentityDetector.PeopleDict"
+	// FactIdentityDetectorReversedPeopleDict is the name of the fact which is inserted in
+	// IdentityDetector.Configure(). It corresponds to IdentityDetector.ReversedPeopleDict -
+	// the mapping from the author indices to the main signature.
 	FactIdentityDetectorReversedPeopleDict = "IdentityDetector.ReversedPeopleDict"
+	// ConfigIdentityDetectorPeopleDictPath is the name of the configuration option
+	// (IdentityDetector.Configure()) which allows to set the external PeopleDict mapping from a file.
 	ConfigIdentityDetectorPeopleDictPath   = "IdentityDetector.PeopleDictPath"
+	// FactIdentityDetectorPeopleCount is the name of the fact which is inserted in
+	// IdentityDetector.Configure(). It is equal to the overall number of unique authors
+	// (the length of ReversedPeopleDict).
 	FactIdentityDetectorPeopleCount        = "IdentityDetector.PeopleCount"
 
+	// DependencyAuthor is the name of the dependency provided by IdentityDetector.
 	DependencyAuthor = "author"
 )
 
@@ -67,10 +84,10 @@ func (id *IdentityDetector) Configure(facts map[string]interface{}) {
 			id.LoadPeopleDict(peopleDictPath)
 			facts[FactIdentityDetectorPeopleCount] = len(id.ReversedPeopleDict) - 1
 		} else {
-			if _, exists := facts[FactPipelineCommits]; !exists {
+			if _, exists := facts[ConfigPipelineCommits]; !exists {
 				panic("IdentityDetector needs a list of commits to initialize.")
 			}
-			id.GeneratePeopleDict(facts[FactPipelineCommits].([]*object.Commit))
+			id.GeneratePeopleDict(facts[ConfigPipelineCommits].([]*object.Commit))
 			facts[FactIdentityDetectorPeopleCount] = len(id.ReversedPeopleDict)
 		}
 	} else {
@@ -83,17 +100,17 @@ func (id *IdentityDetector) Configure(facts map[string]interface{}) {
 func (id *IdentityDetector) Initialize(repository *git.Repository) {
 }
 
-func (self *IdentityDetector) Consume(deps map[string]interface{}) (map[string]interface{}, error) {
+func (id *IdentityDetector) Consume(deps map[string]interface{}) (map[string]interface{}, error) {
 	commit := deps["commit"].(*object.Commit)
 	signature := commit.Author
-	id, exists := self.PeopleDict[strings.ToLower(signature.Email)]
+	authorID, exists := id.PeopleDict[strings.ToLower(signature.Email)]
 	if !exists {
-		id, exists = self.PeopleDict[strings.ToLower(signature.Name)]
+		authorID, exists = id.PeopleDict[strings.ToLower(signature.Name)]
 		if !exists {
-			id = MISSING_AUTHOR
+			authorID = AuthorMissing
 		}
 	}
-	return map[string]interface{}{DependencyAuthor: id}, nil
+	return map[string]interface{}{DependencyAuthor: authorID}, nil
 }
 
 func (id *IdentityDetector) LoadPeopleDict(path string) error {
@@ -104,19 +121,19 @@ func (id *IdentityDetector) LoadPeopleDict(path string) error {
 	defer file.Close()
 	scanner := bufio.NewScanner(file)
 	dict := make(map[string]int)
-	reverse_dict := []string{}
+	reverseDict := []string{}
 	size := 0
 	for scanner.Scan() {
 		ids := strings.Split(scanner.Text(), "|")
 		for _, id := range ids {
 			dict[strings.ToLower(id)] = size
 		}
-		reverse_dict = append(reverse_dict, ids[0])
-		size += 1
+		reverseDict = append(reverseDict, ids[0])
+		size++
 	}
-	reverse_dict = append(reverse_dict, UNMATCHED_AUTHOR)
+	reverseDict = append(reverseDict, AuthorMissingName)
 	id.PeopleDict = dict
-	id.ReversedPeopleDict = reverse_dict
+	id.ReversedPeopleDict = reverseDict
 	return nil
 }
 
@@ -203,20 +220,20 @@ func (id *IdentityDetector) GeneratePeopleDict(commits []*object.Commit) {
 		dict[name] = size
 		emails[size] = append(emails[size], email)
 		names[size] = append(names[size], name)
-		size += 1
+		size++
 	}
-	reverse_dict := make([]string, size)
+	reverseDict := make([]string, size)
 	for _, val := range dict {
 		sort.Strings(names[val])
 		sort.Strings(emails[val])
-		reverse_dict[val] = strings.Join(names[val], "|") + "|" + strings.Join(emails[val], "|")
+		reverseDict[val] = strings.Join(names[val], "|") + "|" + strings.Join(emails[val], "|")
 	}
 	id.PeopleDict = dict
-	id.ReversedPeopleDict = reverse_dict
+	id.ReversedPeopleDict = reverseDict
 }
 
 // MergeReversedDicts joins two identity lists together, excluding duplicates, in-order.
-func (_ IdentityDetector) MergeReversedDicts(rd1, rd2 []string) (map[string][3]int, []string) {
+func (id IdentityDetector) MergeReversedDicts(rd1, rd2 []string) (map[string][3]int, []string) {
 	people := map[string][3]int{}
 	for i, pid := range rd1 {
 		ptrs := people[pid]

+ 3 - 3
identity_test.go

@@ -141,7 +141,7 @@ func TestIdentityDetectorConsume(t *testing.T) {
 	deps["commit"] = commit
 	res, err = fixtureIdentityDetector().Consume(deps)
 	assert.Nil(t, err)
-	assert.Equal(t, res[DependencyAuthor].(int), MISSING_AUTHOR)
+	assert.Equal(t, res[DependencyAuthor].(int), AuthorMissing)
 }
 
 func TestIdentityDetectorLoadPeopleDict(t *testing.T) {
@@ -160,7 +160,7 @@ func TestIdentityDetectorLoadPeopleDict(t *testing.T) {
 	assert.Equal(t, id.ReversedPeopleDict[0], "Linus Torvalds")
 	assert.Equal(t, id.ReversedPeopleDict[1], "Vadim Markovtsev")
 	assert.Equal(t, id.ReversedPeopleDict[2], "Máximo Cuadros")
-	assert.Equal(t, id.ReversedPeopleDict[3], UNMATCHED_AUTHOR)
+	assert.Equal(t, id.ReversedPeopleDict[3], AuthorMissingName)
 }
 
 /*
@@ -229,7 +229,7 @@ func TestIdentityDetectorGeneratePeopleDict(t *testing.T) {
 	assert.Equal(t, id.ReversedPeopleDict[0], "vadim markovtsev|gmarkhor@gmail.com|vadim@sourced.tech")
 	assert.Equal(t, id.ReversedPeopleDict[1], "alexander bezzubov|bzz@apache.org")
 	assert.Equal(t, id.ReversedPeopleDict[2], "máximo cuadros|mcuadros@gmail.com")
-	assert.NotEqual(t, id.ReversedPeopleDict[len(id.ReversedPeopleDict)-1], UNMATCHED_AUTHOR)
+	assert.NotEqual(t, id.ReversedPeopleDict[len(id.ReversedPeopleDict)-1], AuthorMissingName)
 }
 
 func TestIdentityDetectorLoadPeopleDictInvalidPath(t *testing.T) {

+ 19 - 12
pipeline.go

@@ -22,11 +22,11 @@ import (
 type ConfigurationOptionType int
 
 const (
-	// Boolean value type.
+	// BoolConfigurationOption reflects the boolean value type.
 	BoolConfigurationOption ConfigurationOptionType = iota
-	// Integer value type.
+	// IntConfigurationOption reflects the integer value type.
 	IntConfigurationOption
-	// String value type.
+	// StringConfigurationOption reflects the string value type.
 	StringConfigurationOption
 )
 
@@ -167,7 +167,7 @@ func (car *CommonAnalysisResult) FillMetadata(meta *pb.Metadata) *pb.Metadata {
 	return meta
 }
 
-// MetadataToCommonAnalysisResult() copies the data from a Protobuf message.
+// MetadataToCommonAnalysisResult copies the data from a Protobuf message.
 func MetadataToCommonAnalysisResult(meta *pb.Metadata) *CommonAnalysisResult {
 	return &CommonAnalysisResult{
 		BeginTime:     meta.BeginUnixTime,
@@ -177,8 +177,8 @@ func MetadataToCommonAnalysisResult(meta *pb.Metadata) *CommonAnalysisResult {
 	}
 }
 
-// The core Hercules entity which carries several PipelineItems and executes them.
-// See the extended example of how a Pipeline works in doc.go.
+// Pipeline is the core Hercules entity which carries several PipelineItems and executes them.
+// See the extended example of how a Pipeline works in doc.go
 type Pipeline struct {
 	// OnProgress is the callback which is invoked in Analyse() to output it's
 	// progress. The first argument is the number of processed commits and the
@@ -200,15 +200,20 @@ type Pipeline struct {
 }
 
 const (
-	// Makes Pipeline to save the DAG to the specified file.
+	// ConfigPipelineDumpPath is the name of the Pipeline configuration option (Pipeline.Initialize())
+	// which enables saving the items DAG to the specified file.
 	ConfigPipelineDumpPath = "Pipeline.DumpPath"
-	// Disables Configure() and Initialize() invokation on each PipelineItem during the initialization.
+	// ConfigPipelineDryRun is the name of the Pipeline configuration option (Pipeline.Initialize())
+	// which disables Configure() and Initialize() invocation on each PipelineItem during the
+	// Pipeline initialization.
 	// Subsequent Run() calls are going to fail. Useful with ConfigPipelineDumpPath=true.
 	ConfigPipelineDryRun = "Pipeline.DryRun"
-	// Allows to specify the custom commit chain. By default, Pipeline.Commits() is used.
-	FactPipelineCommits = "commits"
+	// ConfigPipelineCommits is the name of the Pipeline configuration option (Pipeline.Initialize())
+	// which allows to specify the custom commit sequence. By default, Pipeline.Commits() is used.
+	ConfigPipelineCommits = "commits"
 )
 
+// NewPipeline initializes a new instance of Pipeline struct.
 func NewPipeline(repository *git.Repository) *Pipeline {
 	return &Pipeline{
 		repository: repository,
@@ -481,8 +486,8 @@ func (pipeline *Pipeline) Initialize(facts map[string]interface{}) {
 	if facts == nil {
 		facts = map[string]interface{}{}
 	}
-	if _, exists := facts[FactPipelineCommits]; !exists {
-		facts[FactPipelineCommits] = pipeline.Commits()
+	if _, exists := facts[ConfigPipelineCommits]; !exists {
+		facts[ConfigPipelineCommits] = pipeline.Commits()
 	}
 	dumpPath, _ := facts[ConfigPipelineDumpPath].(string)
 	pipeline.resolve(dumpPath)
@@ -546,6 +551,8 @@ func (pipeline *Pipeline) Run(commits []*object.Commit) (map[LeafPipelineItem]in
 	return result, nil
 }
 
+// LoadCommitsFromFile reads the file by the specified FS path and generates the sequence of commits
+// by interpreting each line as a Git commit hash.
 func LoadCommitsFromFile(path string, repository *git.Repository) ([]*object.Commit, error) {
 	var file io.ReadCloser
 	if path != "-" {

+ 1 - 2
pipeline_test.go

@@ -135,9 +135,8 @@ func (item *dependingTestPipelineItem) Consume(deps map[string]interface{}) (map
 	item.DependencySatisfied = exists
 	if !item.TestNilConsumeReturn {
 		return map[string]interface{}{"test2": item}, nil
-	} else {
-		return nil, nil
 	}
+	return nil, nil
 }
 
 func (item *dependingTestPipelineItem) Finalize() interface{} {

+ 1 - 2
registry.go

@@ -1,7 +1,6 @@
 package hercules
 
 import (
-	"errors"
 	"fmt"
 	"reflect"
 	"sort"
@@ -129,7 +128,7 @@ func (acf *arrayFeatureFlags) String() string {
 
 func (acf *arrayFeatureFlags) Set(value string) error {
 	if _, exists := acf.Choices[value]; !exists {
-		return errors.New(fmt.Sprintf("Feature \"%s\" is not registered.", value))
+		return fmt.Errorf("feature \"%s\" is not registered", value)
 	}
 	acf.Flags = append(acf.Flags, value)
 	return nil

+ 56 - 49
renames.go

@@ -13,6 +13,9 @@ import (
 	"gopkg.in/src-d/go-git.v4/utils/merkletrie"
 )
 
+// RenameAnalysis improves TreeDiff's results by searching for changed blobs under different
+// paths which are likely to be the result of a rename with subsequent edits.
+// RenameAnalysis is a PipelineItem.
 type RenameAnalysis struct {
 	// SimilarityThreshold adjusts the heuristic to determine file renames.
 	// It has the same units as cgit's -X rename-threshold or -M. Better to
@@ -23,8 +26,12 @@ type RenameAnalysis struct {
 }
 
 const (
-	RENAME_ANALYSIS_DEFAULT_THRESHOLD = 90
+	// RenameAnalysisDefaultThreshold specifies the default percentage of common lines in a pair
+	// of files to consider them linked. The exact code of the decision is sizesAreClose().
+	RenameAnalysisDefaultThreshold = 90
 
+	// ConfigRenameAnalysisSimilarityThreshold is the name of the configuration option
+	// (RenameAnalysis.Configure()) which sets the similarity threshold.
 	ConfigRenameAnalysisSimilarityThreshold = "RenameAnalysis.SimilarityThreshold"
 )
 
@@ -48,7 +55,7 @@ func (ra *RenameAnalysis) ListConfigurationOptions() []ConfigurationOption {
 		Description: "The threshold on the similarity index used to detect renames.",
 		Flag:        "M",
 		Type:        IntConfigurationOption,
-		Default:     RENAME_ANALYSIS_DEFAULT_THRESHOLD},
+		Default:     RenameAnalysisDefaultThreshold},
 	}
 	return options[:]
 }
@@ -62,8 +69,8 @@ func (ra *RenameAnalysis) Configure(facts map[string]interface{}) {
 func (ra *RenameAnalysis) Initialize(repository *git.Repository) {
 	if ra.SimilarityThreshold < 0 || ra.SimilarityThreshold > 100 {
 		fmt.Fprintf(os.Stderr, "Warning: adjusted the similarity threshold to %d\n",
-			RENAME_ANALYSIS_DEFAULT_THRESHOLD)
-		ra.SimilarityThreshold = RENAME_ANALYSIS_DEFAULT_THRESHOLD
+			RenameAnalysisDefaultThreshold)
+		ra.SimilarityThreshold = RenameAnalysisDefaultThreshold
 	}
 	ra.repository = repository
 }
@@ -72,7 +79,7 @@ func (ra *RenameAnalysis) Consume(deps map[string]interface{}) (map[string]inter
 	changes := deps[DependencyTreeChanges].(object.Changes)
 	cache := deps[DependencyBlobCache].(map[plumbing.Hash]*object.Blob)
 
-	reduced_changes := make(object.Changes, 0, changes.Len())
+	reducedChanges := make(object.Changes, 0, changes.Len())
 
 	// Stage 1 - find renames by matching the hashes
 	// n log(n)
@@ -91,92 +98,92 @@ func (ra *RenameAnalysis) Consume(deps map[string]interface{}) (map[string]inter
 		case merkletrie.Delete:
 			deleted = append(deleted, sortableChange{change, change.From.TreeEntry.Hash})
 		case merkletrie.Modify:
-			reduced_changes = append(reduced_changes, change)
+			reducedChanges = append(reducedChanges, change)
 		}
 	}
 	sort.Sort(deleted)
 	sort.Sort(added)
 	a := 0
 	d := 0
-	still_deleted := make(object.Changes, 0, deleted.Len())
-	still_added := make(object.Changes, 0, added.Len())
+	stillDeleted := make(object.Changes, 0, deleted.Len())
+	stillAdded := make(object.Changes, 0, added.Len())
 	for a < added.Len() && d < deleted.Len() {
 		if added[a].hash == deleted[d].hash {
-			reduced_changes = append(
-				reduced_changes,
+			reducedChanges = append(
+				reducedChanges,
 				&object.Change{From: deleted[d].change.From, To: added[a].change.To})
 			a++
 			d++
 		} else if added[a].Less(&deleted[d]) {
-			still_added = append(still_added, added[a].change)
+			stillAdded = append(stillAdded, added[a].change)
 			a++
 		} else {
-			still_deleted = append(still_deleted, deleted[d].change)
+			stillDeleted = append(stillDeleted, deleted[d].change)
 			d++
 		}
 	}
 	for ; a < added.Len(); a++ {
-		still_added = append(still_added, added[a].change)
+		stillAdded = append(stillAdded, added[a].change)
 	}
 	for ; d < deleted.Len(); d++ {
-		still_deleted = append(still_deleted, deleted[d].change)
+		stillDeleted = append(stillDeleted, deleted[d].change)
 	}
 
 	// Stage 2 - apply the similarity threshold
 	// n^2 but actually linear
 	// We sort the blobs by size and do the single linear scan.
-	added_blobs := make(sortableBlobs, 0, still_added.Len())
-	deleted_blobs := make(sortableBlobs, 0, still_deleted.Len())
-	for _, change := range still_added {
+	addedBlobs := make(sortableBlobs, 0, stillAdded.Len())
+	deletedBlobs := make(sortableBlobs, 0, stillDeleted.Len())
+	for _, change := range stillAdded {
 		blob := cache[change.To.TreeEntry.Hash]
-		added_blobs = append(
-			added_blobs, sortableBlob{change: change, size: blob.Size})
+		addedBlobs = append(
+			addedBlobs, sortableBlob{change: change, size: blob.Size})
 	}
-	for _, change := range still_deleted {
+	for _, change := range stillDeleted {
 		blob := cache[change.From.TreeEntry.Hash]
-		deleted_blobs = append(
-			deleted_blobs, sortableBlob{change: change, size: blob.Size})
+		deletedBlobs = append(
+			deletedBlobs, sortableBlob{change: change, size: blob.Size})
 	}
-	sort.Sort(added_blobs)
-	sort.Sort(deleted_blobs)
-	d_start := 0
-	for a = 0; a < added_blobs.Len(); a++ {
-		my_blob := cache[added_blobs[a].change.To.TreeEntry.Hash]
-		my_size := added_blobs[a].size
-		for d = d_start; d < deleted_blobs.Len() && !ra.sizesAreClose(my_size, deleted_blobs[d].size); d++ {
+	sort.Sort(addedBlobs)
+	sort.Sort(deletedBlobs)
+	dStart := 0
+	for a = 0; a < addedBlobs.Len(); a++ {
+		myBlob := cache[addedBlobs[a].change.To.TreeEntry.Hash]
+		mySize := addedBlobs[a].size
+		for d = dStart; d < deletedBlobs.Len() && !ra.sizesAreClose(mySize, deletedBlobs[d].size); d++ {
 		}
-		d_start = d
-		found_match := false
-		for d = d_start; d < deleted_blobs.Len() && ra.sizesAreClose(my_size, deleted_blobs[d].size); d++ {
+		dStart = d
+		foundMatch := false
+		for d = dStart; d < deletedBlobs.Len() && ra.sizesAreClose(mySize, deletedBlobs[d].size); d++ {
 			blobsAreClose, err := ra.blobsAreClose(
-				my_blob, cache[deleted_blobs[d].change.From.TreeEntry.Hash])
+				myBlob, cache[deletedBlobs[d].change.From.TreeEntry.Hash])
 			if err != nil {
 				return nil, err
 			}
 			if blobsAreClose {
-				found_match = true
-				reduced_changes = append(
-					reduced_changes,
-					&object.Change{From: deleted_blobs[d].change.From,
-						To: added_blobs[a].change.To})
+				foundMatch = true
+				reducedChanges = append(
+					reducedChanges,
+					&object.Change{From: deletedBlobs[d].change.From,
+						To:                addedBlobs[a].change.To})
 				break
 			}
 		}
-		if found_match {
-			added_blobs = append(added_blobs[:a], added_blobs[a+1:]...)
+		if foundMatch {
+			addedBlobs = append(addedBlobs[:a], addedBlobs[a+1:]...)
 			a--
-			deleted_blobs = append(deleted_blobs[:d], deleted_blobs[d+1:]...)
+			deletedBlobs = append(deletedBlobs[:d], deletedBlobs[d+1:]...)
 		}
 	}
 
 	// Stage 3 - we give up, everything left are independent additions and deletions
-	for _, blob := range added_blobs {
-		reduced_changes = append(reduced_changes, blob.change)
+	for _, blob := range addedBlobs {
+		reducedChanges = append(reducedChanges, blob.change)
 	}
-	for _, blob := range deleted_blobs {
-		reduced_changes = append(reduced_changes, blob.change)
+	for _, blob := range deletedBlobs {
+		reducedChanges = append(reducedChanges, blob.change)
 	}
-	return map[string]interface{}{DependencyTreeChanges: reduced_changes}, nil
+	return map[string]interface{}{DependencyTreeChanges: reducedChanges}, nil
 }
 
 func (ra *RenameAnalysis) sizesAreClose(size1 int64, size2 int64) bool {
@@ -186,16 +193,16 @@ func (ra *RenameAnalysis) sizesAreClose(size1 int64, size2 int64) bool {
 
 func (ra *RenameAnalysis) blobsAreClose(
 	blob1 *object.Blob, blob2 *object.Blob) (bool, error) {
-	str_from, err := BlobToString(blob1)
+	strFrom, err := BlobToString(blob1)
 	if err != nil {
 		return false, err
 	}
-	str_to, err := BlobToString(blob2)
+	strTo, err := BlobToString(blob2)
 	if err != nil {
 		return false, err
 	}
 	dmp := diffmatchpatch.New()
-	src, dst, _ := dmp.DiffLinesToRunes(str_from, str_to)
+	src, dst, _ := dmp.DiffLinesToRunes(strFrom, strTo)
 	diffs := dmp.DiffMainRunes(src, dst, false)
 	common := 0
 	for _, edit := range diffs {

+ 2 - 2
renames_test.go

@@ -51,10 +51,10 @@ func TestRenameAnalysisRegistration(t *testing.T) {
 func TestRenameAnalysisInitializeInvalidThreshold(t *testing.T) {
 	ra := RenameAnalysis{SimilarityThreshold: -10}
 	ra.Initialize(testRepository)
-	assert.Equal(t, ra.SimilarityThreshold, RENAME_ANALYSIS_DEFAULT_THRESHOLD)
+	assert.Equal(t, ra.SimilarityThreshold, RenameAnalysisDefaultThreshold)
 	ra = RenameAnalysis{SimilarityThreshold: 110}
 	ra.Initialize(testRepository)
-	assert.Equal(t, ra.SimilarityThreshold, RENAME_ANALYSIS_DEFAULT_THRESHOLD)
+	assert.Equal(t, ra.SimilarityThreshold, RenameAnalysisDefaultThreshold)
 	ra = RenameAnalysis{SimilarityThreshold: 0}
 	ra.Initialize(testRepository)
 	ra = RenameAnalysis{SimilarityThreshold: 100}

+ 13 - 2
shotness.go

@@ -27,10 +27,19 @@ type ShotnessAnalysis struct {
 }
 
 const (
+	// ConfigShotnessXpathStruct is the name of the configuration option (ShotnessAnalysis.Configure())
+	// which sets the UAST XPath to choose the analysed nodes.
 	ConfigShotnessXpathStruct = "Shotness.XpathStruct"
+	// ConfigShotnessXpathName is the name of the configuration option (ShotnessAnalysis.Configure())
+	// which sets the UAST XPath to find the name of the nodes chosen by ConfigShotnessXpathStruct.
+	// These XPath-s can be different for some languages.
 	ConfigShotnessXpathName   = "Shotness.XpathName"
 
+	// DefaultShotnessXpathStruct is the default UAST XPath to choose the analysed nodes.
+	// It extracts functions.
 	DefaultShotnessXpathStruct = "//*[@roleFunction and @roleDeclaration]"
+	// DefaultShotnessXpathName is the default UAST XPath to choose the names of the analysed nodes.
+	// It looks at the current tree level and at the immediate children.
 	DefaultShotnessXpathName   = "/*[@roleFunction and @roleIdentifier and @roleName] | /*/*[@roleFunction and @roleIdentifier and @roleName]"
 )
 
@@ -40,6 +49,8 @@ type nodeShotness struct {
 	Couples map[string]int
 }
 
+// NodeSummary carries the node attributes which annotate the "shotness" analysis' counters.
+// These attributes are supposed to uniquely identify each node.
 type NodeSummary struct {
 	InternalRole string
 	Roles        []uast.Role
@@ -47,7 +58,7 @@ type NodeSummary struct {
 	File         string
 }
 
-// ShotnessResult is returned by Finalize() and represents the analysis result.
+// ShotnessResult is returned by ShotnessAnalysis.Finalize() and represents the analysis result.
 type ShotnessResult struct {
 	Nodes    []NodeSummary
 	Counters []map[int]int
@@ -65,7 +76,7 @@ func (shotness *ShotnessAnalysis) Provides() []string {
 	return []string{}
 }
 
-func (ref *ShotnessAnalysis) Features() []string {
+func (shotness *ShotnessAnalysis) Features() []string {
 	arr := [...]string{FeatureUast}
 	return arr[:]
 }

+ 8 - 3
tree_diff.go

@@ -7,11 +7,16 @@ import (
 	"gopkg.in/src-d/go-git.v4/plumbing/object"
 )
 
+// TreeDiff generates the list of changes for a commit. A change can be either one or two blobs
+// under the same path: "before" and "after". If "before" is nil, the change is an addition.
+// If "after" is nil, the change is a removal. Otherwise, it is a modification.
+// TreeDiff is a PipelineItem.
 type TreeDiff struct {
 	previousTree *object.Tree
 }
 
 const (
+	// DependencyTreeChanges is the name of the dependency provided by TreeDiff.
 	DependencyTreeChanges = "changes"
 )
 
@@ -53,10 +58,10 @@ func (treediff *TreeDiff) Consume(deps map[string]interface{}) (map[string]inter
 	} else {
 		diff = []*object.Change{}
 		err = func() error {
-			file_iter := tree.Files()
-			defer file_iter.Close()
+			fileIter := tree.Files()
+			defer fileIter.Close()
 			for {
-				file, err := file_iter.Next()
+				file, err := fileIter.Next()
 				if err != nil {
 					if err == io.EOF {
 						break

+ 26 - 4
uast.go

@@ -28,6 +28,8 @@ import (
 	"gopkg.in/src-d/hercules.v3/pb"
 )
 
+// UASTExtractor retrieves UASTs from Babelfish server which correspond to changed files in a commit.
+// It is a PipelineItem.
 type UASTExtractor struct {
 	Endpoint       string
 	Context        func() (context.Context, context.CancelFunc)
@@ -41,15 +43,28 @@ type UASTExtractor struct {
 }
 
 const (
-	UAST_EXTRACTION_SKIPPED = -(1 << 31)
+	uastExtractionSkipped = -(1 << 31)
 
+	// ConfigUASTEndpoint is the name of the configuration option (UASTExtractor.Configure())
+	// which sets the Babelfish server address.
 	ConfigUASTEndpoint     = "ConfigUASTEndpoint"
+	// ConfigUASTTimeout is the name of the configuration option (UASTExtractor.Configure())
+	// which sets the maximum amount of time to wait for a Babelfish server response.
 	ConfigUASTTimeout      = "ConfigUASTTimeout"
+	// ConfigUASTPoolSize is the name of the configuration option (UASTExtractor.Configure())
+	// which sets the number of goroutines to run for UAST parse queries.
 	ConfigUASTPoolSize     = "ConfigUASTPoolSize"
+	// ConfigUASTFailOnErrors is the name of the configuration option (UASTExtractor.Configure())
+	// which enables early exit in case of any Babelfish UAST parsing errors.
 	ConfigUASTFailOnErrors = "ConfigUASTFailOnErrors"
+	// ConfigUASTLanguages is the name of the configuration option (UASTExtractor.Configure())
+	// which sets the list of languages to parse. Language names are at
+	// https://doc.bblf.sh/languages.html Names are joined with a comma ",".
 	ConfigUASTLanguages    = "ConfigUASTLanguages"
 
+	// FeatureUast is the name of the Pipeline feature which activates all the items related to UAST.
 	FeatureUast     = "uast"
+	// DependencyUasts is the name of the dependency provided by UASTExtractor.
 	DependencyUasts = "uasts"
 )
 
@@ -211,7 +226,7 @@ func (exr *UASTExtractor) Consume(deps map[string]interface{}) (map[string]inter
 			}
 			lang := enry.GetLanguage(change.To.Name, buf.Bytes())
 			if _, exists := exr.Languages[lang]; !exists {
-				exr.ProcessedFiles[change.To.Name] = UAST_EXTRACTION_SKIPPED
+				exr.ProcessedFiles[change.To.Name] = uastExtractionSkipped
 				return
 			}
 			exr.ProcessedFiles[change.To.Name]++
@@ -248,9 +263,8 @@ func (exr *UASTExtractor) Consume(deps map[string]interface{}) (map[string]inter
 		joined := strings.Join(msgs, "\n")
 		if exr.FailOnErrors {
 			return nil, errors.New(joined)
-		} else {
-			fmt.Fprintln(os.Stderr, joined)
 		}
+		fmt.Fprintln(os.Stderr, joined)
 	}
 	return map[string]interface{}{DependencyUasts: uasts}, nil
 }
@@ -300,6 +314,7 @@ func (exr *UASTExtractor) extractTask(data interface{}) interface{} {
 	return nil
 }
 
+// UASTChange is the type of the items in the list of changes which is provided by UASTChanges.
 type UASTChange struct {
 	Before *uast.Node
 	After  *uast.Node
@@ -307,9 +322,12 @@ type UASTChange struct {
 }
 
 const (
+	// DependencyUastChanges is the name of the dependency provided by UASTChanges.
 	DependencyUastChanges = "changed_uasts"
 )
 
+// UASTChanges is a structured analog of TreeDiff: it provides UASTs for every logical change
+// in a commit. It is a PipelineItem.
 type UASTChanges struct {
 	cache map[plumbing.Hash]*uast.Node
 }
@@ -374,6 +392,8 @@ func (uc *UASTChanges) Consume(deps map[string]interface{}) (map[string]interfac
 	return map[string]interface{}{DependencyUastChanges: commit}, nil
 }
 
+// UASTChangesSaver dumps changed files and corresponding UASTs for every commit.
+// it is a LeafPipelineItem.
 type UASTChangesSaver struct {
 	// OutputPath points to the target directory with UASTs
 	OutputPath string
@@ -383,6 +403,8 @@ type UASTChangesSaver struct {
 }
 
 const (
+	// ConfigUASTChangesSaverOutputPath is the name of the configuration option
+	// (UASTChangesSaver.Configure()) which sets the target directory where to save the files.
 	ConfigUASTChangesSaverOutputPath = "UASTChangesSaver.OutputPath"
 )
 

+ 5 - 3
version.go

@@ -6,13 +6,15 @@ import (
 	"strings"
 )
 
-var GIT_HASH = "<unknown>"
+// BinaryGitHash is the Git hash of the Hercules binary file which is executing.
+var BinaryGitHash = "<unknown>"
 
-var VERSION = 0
+// BinaryVersion is Hercules' API version. It matches the package name.
+var BinaryVersion = 0
 
 type versionProbe struct{}
 
 func init() {
 	parts := strings.Split(reflect.TypeOf(versionProbe{}).PkgPath(), ".")
-	VERSION, _ = strconv.Atoi(parts[len(parts)-1][1:])
+	BinaryVersion, _ = strconv.Atoi(parts[len(parts)-1][1:])
 }