Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions pkg/git/git.go
Original file line number Diff line number Diff line change
Expand Up @@ -255,7 +255,8 @@ func ShellToUse() string {
// GetContributors returns all commit authors and committers with dates from git history
func GetContributors() ([]string, error) {
// Execute the git command to get all contributors with their commit dates
command := exec.Command("git", "log", "--all", "--format=%an|%cn|%cd", "--date=format:%Y")
// Using %aN and %cN format specifiers to respect .mailmap files for consistent author/committer names
command := exec.Command("git", "log", "--all", "--format=%aN|%cN|%cd", "--date=format:%Y")
output, err := command.Output()
if err != nil {
return nil, err
Expand Down Expand Up @@ -444,7 +445,8 @@ func GetRateOfChanges() (map[int]models.RateStatistics, string, error) {
}

// Get all commits from current branch with timestamps, merge info, and authors
command := exec.Command("git", "log", currentBranch, "--format=%ct|%P|%an", "--reverse")
// Using %aN format specifier to respect .mailmap files for consistent author names
command := exec.Command("git", "log", currentBranch, "--format=%ct|%P|%aN", "--reverse")
output, err := command.Output()
if err != nil {
return nil, "", fmt.Errorf("failed to get commit log: %v", err)
Expand Down
111 changes: 111 additions & 0 deletions pkg/git/git_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -110,3 +110,114 @@ func TestGetGitDirectory(t *testing.T) {
func mockRunGitCommand(_ bool, _ ...string) ([]byte, error) {
return []byte("git version 2.35.1"), nil
}

func TestMailmapSupport(t *testing.T) {
// Create a temporary directory for the test repository
tempDir, err := os.MkdirTemp("", "git-repo-mailmap-test")
if err != nil {
t.Fatalf("Failed to create temp directory: %v", err)
}
defer os.RemoveAll(tempDir)

// Save current directory
originalDir, err := os.Getwd()
if err != nil {
t.Fatalf("Failed to get current directory: %v", err)
}
defer os.Chdir(originalDir)

// Change to temp directory
if err := os.Chdir(tempDir); err != nil {
t.Fatalf("Failed to change to temp directory: %v", err)
}

// Initialize git repository
if err := exec.Command("git", "init").Run(); err != nil {
t.Fatalf("Failed to initialize git repository: %v", err)
}

// Configure git user for commits
if err := exec.Command("git", "config", "user.email", "test@example.com").Run(); err != nil {
t.Fatalf("Failed to configure git user email: %v", err)
}
if err := exec.Command("git", "config", "user.name", "Test User").Run(); err != nil {
t.Fatalf("Failed to configure git user name: %v", err)
}

// Create a commit with one author name/email
if err := os.WriteFile("file1.txt", []byte("content1"), 0644); err != nil {
t.Fatalf("Failed to create file1.txt: %v", err)
}
if err := exec.Command("git", "add", "file1.txt").Run(); err != nil {
t.Fatalf("Failed to add file1.txt: %v", err)
}
if err := exec.Command("git", "-c", "user.name=John Doe", "-c", "user.email=john@example.com", "commit", "-m", "First commit").Run(); err != nil {
t.Fatalf("Failed to create first commit: %v", err)
}

// Create another commit with a different name/email for the same person
if err := os.WriteFile("file2.txt", []byte("content2"), 0644); err != nil {
t.Fatalf("Failed to create file2.txt: %v", err)
}
if err := exec.Command("git", "add", "file2.txt").Run(); err != nil {
t.Fatalf("Failed to add file2.txt: %v", err)
}
if err := exec.Command("git", "-c", "user.name=J. Doe", "-c", "user.email=jdoe@example.com", "commit", "-m", "Second commit").Run(); err != nil {
t.Fatalf("Failed to create second commit: %v", err)
}

// Test without .mailmap - should see two different authors
contributors, err := GetContributors()
if err != nil {
t.Fatalf("GetContributors() failed: %v", err)
}

// Count unique authors from the output
authorsWithoutMailmap := make(map[string]bool)
for _, line := range contributors {
line = strings.TrimSpace(line)
if line == "" {
continue
}
parts := strings.Split(line, "|")
if len(parts) >= 1 {
authorsWithoutMailmap[parts[0]] = true
}
}

// Create .mailmap file to map both identities to one
mailmapContent := "John Doe <john@example.com> J. Doe <jdoe@example.com>\n"
if err := os.WriteFile(".mailmap", []byte(mailmapContent), 0644); err != nil {
t.Fatalf("Failed to create .mailmap: %v", err)
}

// Test with .mailmap - should see only one author
contributors, err = GetContributors()
if err != nil {
t.Fatalf("GetContributors() with mailmap failed: %v", err)
}

// Count unique authors with mailmap
authorsWithMailmap := make(map[string]bool)
for _, line := range contributors {
line = strings.TrimSpace(line)
if line == "" {
continue
}
parts := strings.Split(line, "|")
if len(parts) >= 1 {
authorsWithMailmap[parts[0]] = true
}
}

// With mailmap, we should have only 1 unique author (John Doe)
// Without checking this would fail if git doesn't respect --use-mailmap
if len(authorsWithMailmap) != 1 {
t.Errorf("Expected 1 unique author with mailmap, got %d: %v", len(authorsWithMailmap), authorsWithMailmap)
}

// Verify the consolidated name is "John Doe"
if !authorsWithMailmap["John Doe"] {
t.Errorf("Expected author 'John Doe' to be present, got: %v", authorsWithMailmap)
}
}
Loading