mirror of
https://github.com/lukaszraczylo/git-velocity.git
synced 2026-06-06 22:49:27 +00:00
3bd9807e50
Git Level (per commit):
- Track unique file paths in FilesModified slice
- FilesChanged = count of unique files in THIS commit
Aggregator Level (per contributor):
- Collect all file paths from all commits into a SET
- FilesChanged = size of the unique file set
Result:
- Contributor.FilesChanged = count of UNIQUE files they touched
- Repository contributor = unique files in THAT repo only
179 lines
5.4 KiB
Go
179 lines
5.4 KiB
Go
package diff
|
|
|
|
import (
|
|
"strings"
|
|
)
|
|
|
|
// IsCommentLine checks if a line is a code comment (should not count as meaningful contribution)
|
|
// Note: Empty/whitespace lines are NOT comments - use IsWhitespaceLine for those.
|
|
func IsCommentLine(line string) bool {
|
|
trimmed := strings.TrimSpace(line)
|
|
if trimmed == "" {
|
|
return false // Empty lines are whitespace, not comments
|
|
}
|
|
|
|
// Common comment patterns across languages
|
|
// Order matters for overlapping prefixes (e.g., "///" before "//")
|
|
commentPrefixes := []string{
|
|
"///", // Rust/Swift/C# doc comments
|
|
"//", // C, C++, Java, Go, JS, TS, Swift, Kotlin, etc.
|
|
"#", // Python, Ruby, Shell, YAML, Perl, etc.
|
|
"/**", // JSDoc/JavaDoc block start
|
|
"/*", // C-style block comment start
|
|
"*/", // C-style block comment end
|
|
"<!--", // HTML/XML comment
|
|
"-->", // HTML/XML comment end
|
|
"--", // SQL, Lua, Haskell
|
|
";", // Assembly, Lisp, INI files
|
|
"'", // VB comment
|
|
"\"\"\"", // Python docstring
|
|
"'''", // Python docstring
|
|
}
|
|
|
|
for _, prefix := range commentPrefixes {
|
|
if strings.HasPrefix(trimmed, prefix) {
|
|
return true
|
|
}
|
|
}
|
|
|
|
// C-style block comment continuation: line starts with * followed by space or end of line
|
|
// This avoids false positives like "*ptr = value" (pointer dereference)
|
|
if strings.HasPrefix(trimmed, "*") {
|
|
if len(trimmed) == 1 {
|
|
return true // Just "*" alone
|
|
}
|
|
// Must be followed by whitespace or common comment characters, not alphanumeric
|
|
nextChar := trimmed[1]
|
|
if nextChar == ' ' || nextChar == '\t' || nextChar == '/' {
|
|
return true
|
|
}
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
// IsWhitespaceLine checks if a line contains only whitespace characters
|
|
func IsWhitespaceLine(line string) bool {
|
|
return strings.TrimSpace(line) == ""
|
|
}
|
|
|
|
// IsDocumentationFile checks if a file is documentation-only
|
|
func IsDocumentationFile(filename string) bool {
|
|
// Documentation file extensions and patterns
|
|
docPatterns := []string{
|
|
".md", ".markdown", ".rst", ".txt", ".adoc",
|
|
"README", "CHANGELOG", "LICENSE", "CONTRIBUTING",
|
|
"docs/", "documentation/", "/doc/",
|
|
}
|
|
|
|
lowerFilename := strings.ToLower(filename)
|
|
for _, pattern := range docPatterns {
|
|
if strings.Contains(lowerFilename, strings.ToLower(pattern)) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// IsMeaningfulLine checks if a line of code is meaningful (not a comment or whitespace)
|
|
func IsMeaningfulLine(line string) bool {
|
|
return !IsWhitespaceLine(line) && !IsCommentLine(line)
|
|
}
|
|
|
|
// IsDocCommentLine checks if a line is a documentation comment (JSDoc, JavaDoc, Rust doc, etc.)
|
|
// These are comments specifically meant to document code, as opposed to regular comments.
|
|
func IsDocCommentLine(line string) bool {
|
|
trimmed := strings.TrimSpace(line)
|
|
if trimmed == "" {
|
|
return false
|
|
}
|
|
|
|
// Documentation comment patterns
|
|
docPrefixes := []string{
|
|
"///", // Rust, Swift, C# doc comments
|
|
"//!", // Rust inner doc comments
|
|
"/**", // JSDoc, JavaDoc block start
|
|
"\"\"\"", // Python docstring
|
|
"'''", // Python docstring
|
|
}
|
|
|
|
for _, prefix := range docPrefixes {
|
|
if strings.HasPrefix(trimmed, prefix) {
|
|
return true
|
|
}
|
|
}
|
|
|
|
// JSDoc/JavaDoc continuation lines with annotations (@param, @return, etc.)
|
|
if strings.HasPrefix(trimmed, "* @") || strings.HasPrefix(trimmed, "* @") {
|
|
return true
|
|
}
|
|
|
|
// Check for common doc annotations at the start of a comment
|
|
if strings.HasPrefix(trimmed, "// @") || strings.HasPrefix(trimmed, "# @") {
|
|
return true
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
// IsCommentedOutCode attempts to detect if a comment line contains commented-out code
|
|
// rather than an actual comment. This is a heuristic and may have false positives/negatives.
|
|
func IsCommentedOutCode(line string) bool {
|
|
trimmed := strings.TrimSpace(line)
|
|
if trimmed == "" {
|
|
return false
|
|
}
|
|
|
|
// Remove comment prefix to get the content
|
|
var content string
|
|
commentPrefixes := []string{"///", "//", "#", "/*", "--", ";"}
|
|
for _, prefix := range commentPrefixes {
|
|
if strings.HasPrefix(trimmed, prefix) {
|
|
content = strings.TrimSpace(trimmed[len(prefix):])
|
|
break
|
|
}
|
|
}
|
|
|
|
if content == "" {
|
|
return false
|
|
}
|
|
|
|
// Heuristics for detecting commented-out code:
|
|
// 1. Ends with common code patterns
|
|
codeEndings := []string{";", "{", "}", ")", ",", ":", "=>", "->"}
|
|
for _, ending := range codeEndings {
|
|
if strings.HasSuffix(content, ending) {
|
|
return true
|
|
}
|
|
}
|
|
|
|
// 2. Starts with common code keywords
|
|
codeKeywords := []string{
|
|
"if ", "else ", "for ", "while ", "switch ", "case ", "return ", "break", "continue",
|
|
"const ", "let ", "var ", "func ", "function ", "def ", "class ", "struct ", "type ",
|
|
"import ", "from ", "package ", "public ", "private ", "protected ", "static ",
|
|
"async ", "await ", "try ", "catch ", "throw ", "raise ",
|
|
}
|
|
contentLower := strings.ToLower(content)
|
|
for _, keyword := range codeKeywords {
|
|
if strings.HasPrefix(contentLower, keyword) {
|
|
return true
|
|
}
|
|
}
|
|
|
|
// 3. Contains assignment operators
|
|
if strings.Contains(content, " = ") || strings.Contains(content, " := ") ||
|
|
strings.Contains(content, " == ") || strings.Contains(content, " != ") {
|
|
return true
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
// IsRenameOrMove checks if a file change represents a rename or move operation
|
|
// rather than actual content modification. A rename/move is detected when both
|
|
// the source (fromName) and destination (toName) paths exist and differ.
|
|
func IsRenameOrMove(fromName, toName string) bool {
|
|
return fromName != "" && toName != "" && fromName != toName
|
|
}
|