Téléverser les fichiers vers "/"
This commit is contained in:
parent
ebd4775d5e
commit
877280aa22
|
|
@ -0,0 +1,15 @@
|
|||
# Binaries
|
||||
PortaGit.exe
|
||||
portagit
|
||||
*.exe
|
||||
*.dll
|
||||
|
||||
# Application Data
|
||||
repositories/
|
||||
uploads/
|
||||
portagit.json
|
||||
*.db
|
||||
|
||||
# OS specific checks
|
||||
.DS_Store
|
||||
thumbs.db
|
||||
|
|
@ -0,0 +1,67 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"math"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
// loadConfig reads the configuration file from the specified working directory.
|
||||
// If the file does not exist or cannot be read, it returns a default configuration.
|
||||
func loadConfig(cwd string) Config {
|
||||
configPath := filepath.Join(cwd, configFileName)
|
||||
file, err := os.ReadFile(configPath)
|
||||
if err != nil {
|
||||
return Config{
|
||||
CustomColors: CustomColors{
|
||||
BgColor: "#0d1117",
|
||||
CardBg: "#161b22",
|
||||
BorderColor: "#30363d",
|
||||
AccentColor: "#58a6ff",
|
||||
TextPrimary: "#c9d1d9",
|
||||
TextSecondary: "#8b949e",
|
||||
TextMuted: "#484f58",
|
||||
ButtonText: "#ffffff",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
var config Config
|
||||
json.Unmarshal(file, &config)
|
||||
if config.CustomColors.BgColor == "" {
|
||||
config.CustomColors = CustomColors{
|
||||
BgColor: "#0d1117",
|
||||
CardBg: "#161b22",
|
||||
BorderColor: "#30363d",
|
||||
AccentColor: "#58a6ff",
|
||||
TextPrimary: "#c9d1d9",
|
||||
TextSecondary: "#8b949e",
|
||||
TextMuted: "#484f58",
|
||||
ButtonText: "#ffffff",
|
||||
}
|
||||
}
|
||||
return config
|
||||
}
|
||||
|
||||
// saveConfig writes the provided configuration to the specified file path.
|
||||
// It marshals the config struct to JSON with indentation.
|
||||
func saveConfig(path string, config Config) {
|
||||
data, err := json.MarshalIndent(config, "", " ")
|
||||
if err != nil {
|
||||
fmt.Println("Error saving config:", err)
|
||||
return
|
||||
}
|
||||
os.WriteFile(path, data, 0644)
|
||||
}
|
||||
|
||||
// humanize converts a byte count into a human-readable string (e.g., "1.2 MiB").
|
||||
func humanize(bytes int64) string {
|
||||
if bytes < 1024 {
|
||||
return fmt.Sprintf("%d B", bytes)
|
||||
}
|
||||
exp := int(math.Log(float64(bytes)) / math.Log(1024))
|
||||
pre := "KMGTPE"[exp-1 : exp]
|
||||
return fmt.Sprintf("%.1f %siB", float64(bytes)/math.Pow(1024, float64(exp)), pre)
|
||||
}
|
||||
|
|
@ -0,0 +1,685 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// migrateRepositories moves existing top-level directories into the "repositories" folder.
|
||||
// It skips specific reserved directories and configuration files.
|
||||
func migrateRepositories(root string) {
|
||||
reposDir := filepath.Join(root, "repositories")
|
||||
if _, err := os.Stat(reposDir); os.IsNotExist(err) {
|
||||
os.MkdirAll(reposDir, 0755)
|
||||
}
|
||||
|
||||
entries, err := os.ReadDir(root)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
exclusions := map[string]bool{
|
||||
"web": true,
|
||||
"repositories": true,
|
||||
".git": true,
|
||||
".agent": true,
|
||||
".gemini": true,
|
||||
"tmp": true,
|
||||
"portagit.json": true,
|
||||
"main.go": true,
|
||||
"go.mod": true,
|
||||
"go.sum": true,
|
||||
"types.go": true,
|
||||
"git_utils.go": true,
|
||||
"config_utils.go": true,
|
||||
}
|
||||
|
||||
// Define a list of reserved file and directory names that should be excluded from the migration process.
|
||||
for _, e := range entries {
|
||||
if e.IsDir() {
|
||||
name := e.Name()
|
||||
if exclusions[name] {
|
||||
continue
|
||||
}
|
||||
|
||||
oldPath := filepath.Join(root, name)
|
||||
if _, err := os.Stat(filepath.Join(oldPath, ".git")); err == nil {
|
||||
newPath := filepath.Join(reposDir, name)
|
||||
fmt.Printf("Migrating repository %s to %s...\n", name, newPath)
|
||||
os.Rename(oldPath, newPath)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// scanRepositories iterates through the repositories folder and collects metadata
|
||||
// for each git repository found (name, description, language, update time).
|
||||
// It utilizes concurrent goroutines to optimize the scanning performance.
|
||||
func scanRepositories(root string) []Repository {
|
||||
var repos []Repository
|
||||
var mu sync.Mutex
|
||||
var wg sync.WaitGroup
|
||||
|
||||
entries, err := os.ReadDir(root)
|
||||
if err != nil {
|
||||
return repos
|
||||
}
|
||||
|
||||
sem := make(chan struct{}, 20)
|
||||
|
||||
for _, e := range entries {
|
||||
if e.IsDir() {
|
||||
wg.Add(1)
|
||||
go func(e os.DirEntry) {
|
||||
defer wg.Done()
|
||||
sem <- struct{}{}
|
||||
defer func() { <-sem }()
|
||||
|
||||
repoPath := filepath.Join(root, e.Name())
|
||||
if _, err := os.Stat(filepath.Join(repoPath, ".git")); err == nil {
|
||||
|
||||
description := ""
|
||||
if descBytes, err := os.ReadFile(filepath.Join(repoPath, ".git", "description")); err == nil {
|
||||
desc := strings.TrimSpace(string(descBytes))
|
||||
if desc != "" && !strings.Contains(desc, "Unnamed repository") {
|
||||
description = desc
|
||||
}
|
||||
}
|
||||
|
||||
updated := "Unknown"
|
||||
cmd := exec.Command("git", "-C", repoPath, "log", "-1", "--format=%ar")
|
||||
if out, err := cmd.Output(); err == nil {
|
||||
updated = strings.TrimSpace(string(out))
|
||||
} else {
|
||||
if info, err := e.Info(); err == nil {
|
||||
updated = info.ModTime().Format("Jan 02, 2006")
|
||||
}
|
||||
}
|
||||
|
||||
// Determine the primary programming language by analyzing file extensions within the repository.
|
||||
language := "Unknown"
|
||||
extCounts := make(map[string]int)
|
||||
|
||||
// Recursively walk through the repository directory tree to count file extensions.
|
||||
filepath.WalkDir(repoPath, func(path string, d os.DirEntry, err error) error {
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
if d.IsDir() {
|
||||
if d.Name() == ".git" || d.Name() == "node_modules" || d.Name() == "vendor" || d.Name() == "dist" || d.Name() == "build" || d.Name() == ".idea" || d.Name() == ".vscode" || d.Name() == "bin" || d.Name() == "obj" {
|
||||
return filepath.SkipDir
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
ext := strings.ToLower(filepath.Ext(d.Name()))
|
||||
switch ext {
|
||||
case ".go":
|
||||
extCounts["Go"]++
|
||||
case ".py":
|
||||
extCounts["Python"]++
|
||||
case ".js", ".mjs", ".jsx":
|
||||
extCounts["JavaScript"]++
|
||||
case ".ts", ".tsx":
|
||||
extCounts["TypeScript"]++
|
||||
case ".html", ".htm":
|
||||
extCounts["HTML"]++
|
||||
case ".css", ".scss", ".less":
|
||||
extCounts["CSS"]++
|
||||
case ".java":
|
||||
extCounts["Java"]++
|
||||
case ".c", ".h":
|
||||
extCounts["C"]++
|
||||
case ".cpp", ".hpp", ".cc":
|
||||
extCounts["C++"]++
|
||||
case ".php":
|
||||
extCounts["PHP"]++
|
||||
case ".rb":
|
||||
extCounts["Ruby"]++
|
||||
case ".rs":
|
||||
extCounts["Rust"]++
|
||||
case ".cs":
|
||||
extCounts["C#"]++
|
||||
case ".swift":
|
||||
extCounts["Swift"]++
|
||||
case ".kt", ".kts":
|
||||
extCounts["Kotlin"]++
|
||||
case ".dart":
|
||||
extCounts["Dart"]++
|
||||
case ".lua":
|
||||
extCounts["Lua"]++
|
||||
case ".sh", ".bash", ".zsh":
|
||||
extCounts["Shell"]++
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
maxCount := 0
|
||||
for lang, count := range extCounts {
|
||||
if count > maxCount {
|
||||
maxCount = count
|
||||
language = lang
|
||||
}
|
||||
}
|
||||
|
||||
mu.Lock()
|
||||
repos = append(repos, Repository{
|
||||
Name: e.Name(),
|
||||
Description: description,
|
||||
Language: language,
|
||||
Path: e.Name(),
|
||||
UpdatedAt: updated,
|
||||
})
|
||||
mu.Unlock()
|
||||
}
|
||||
}(e)
|
||||
}
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
sort.Slice(repos, func(i, j int) bool {
|
||||
return repos[i].Name < repos[j].Name
|
||||
})
|
||||
|
||||
return repos
|
||||
}
|
||||
|
||||
// getBranches retrieves all branches for a given repository and identifies the current active branch.
|
||||
func getBranches(repoPath string) ([]string, string) {
|
||||
var branches []string
|
||||
var current string
|
||||
|
||||
cmd := exec.Command("git", "-C", repoPath, "branch", "-a")
|
||||
out, err := cmd.Output()
|
||||
if err != nil {
|
||||
return branches, ""
|
||||
}
|
||||
|
||||
scanner := bufio.NewScanner(bytes.NewReader(out))
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
line = strings.TrimSpace(line)
|
||||
if strings.HasPrefix(line, "* ") {
|
||||
current = strings.TrimPrefix(line, "* ")
|
||||
branches = append(branches, current)
|
||||
} else {
|
||||
cleanName := strings.TrimSpace(line)
|
||||
if strings.HasPrefix(cleanName, "remotes/origin/") {
|
||||
cleanName = strings.TrimPrefix(cleanName, "remotes/origin/")
|
||||
if cleanName == "HEAD" || strings.HasPrefix(cleanName, "HEAD ->") {
|
||||
continue
|
||||
}
|
||||
}
|
||||
// Avoid duplicates if a branch exists both locally and remotely
|
||||
duplicate := false
|
||||
for _, b := range branches {
|
||||
if b == cleanName {
|
||||
duplicate = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !duplicate {
|
||||
branches = append(branches, cleanName)
|
||||
}
|
||||
}
|
||||
}
|
||||
return branches, current
|
||||
}
|
||||
|
||||
// createBranch creates a new branch in the specified repository.
|
||||
func createBranch(repoPath, branchName string) error {
|
||||
cmd := exec.Command("git", "-C", repoPath, "branch", branchName)
|
||||
return cmd.Run()
|
||||
}
|
||||
|
||||
// getCommits fetches specific metadata for the latest 20 commits of the repository.
|
||||
func getCommits(repoPath string) []Commit {
|
||||
var commits []Commit
|
||||
cmd := exec.Command("git", "-C", repoPath, "log", "--all", "--pretty=format:%h|%s|%an|%ar", "-n", "20")
|
||||
out, err := cmd.Output()
|
||||
if err != nil {
|
||||
return commits
|
||||
}
|
||||
|
||||
scanner := bufio.NewScanner(bytes.NewReader(out))
|
||||
for scanner.Scan() {
|
||||
parts := strings.Split(scanner.Text(), "|")
|
||||
if len(parts) >= 4 {
|
||||
commits = append(commits, Commit{
|
||||
Hash: parts[0],
|
||||
Message: parts[1],
|
||||
Author: parts[2],
|
||||
Date: parts[3],
|
||||
})
|
||||
}
|
||||
}
|
||||
return commits
|
||||
}
|
||||
|
||||
// getCommitsForMonth fetches all commits within a specific month and year.
|
||||
func getCommitsForMonth(repoPath string, year, month int) []Commit {
|
||||
var commits []Commit
|
||||
|
||||
start := time.Date(year, time.Month(month), 1, 0, 0, 0, 0, time.UTC)
|
||||
end := start.AddDate(0, 1, 0)
|
||||
|
||||
since := start.Format("2006-01-02")
|
||||
until := end.Format("2006-01-02")
|
||||
|
||||
cmd := exec.Command("git", "-C", repoPath, "log", "--all",
|
||||
fmt.Sprintf("--since=%s", since),
|
||||
fmt.Sprintf("--until=%s", until),
|
||||
"--pretty=format:%h|%s|%an|%ar|%ad",
|
||||
"--date=short",
|
||||
)
|
||||
|
||||
out, err := cmd.Output()
|
||||
if err != nil {
|
||||
fmt.Printf("Error getting commits for month: %v\n", err)
|
||||
return commits
|
||||
}
|
||||
|
||||
scanner := bufio.NewScanner(bytes.NewReader(out))
|
||||
for scanner.Scan() {
|
||||
parts := strings.Split(scanner.Text(), "|")
|
||||
if len(parts) >= 4 {
|
||||
c := Commit{
|
||||
Hash: parts[0],
|
||||
Message: parts[1],
|
||||
Author: parts[2],
|
||||
Date: parts[3],
|
||||
}
|
||||
|
||||
commits = append(commits, c)
|
||||
}
|
||||
}
|
||||
return commits
|
||||
}
|
||||
|
||||
// getCommitDiff retrieves the file changes (diffs) for a specific commit hash.
|
||||
func getCommitDiff(repoPath, hash string) []FileDiff {
|
||||
cmd := exec.Command("git", "-C", repoPath, "show", hash, "--name-only", "--format=")
|
||||
out, err := cmd.Output()
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
files := strings.Split(strings.TrimSpace(string(out)), "\n")
|
||||
var diffs []FileDiff
|
||||
|
||||
for _, file := range files {
|
||||
if file == "" {
|
||||
continue
|
||||
}
|
||||
diffCmd := exec.Command("git", "-C", repoPath, "show", hash, "--", file)
|
||||
diffOut, _ := diffCmd.Output()
|
||||
diffs = append(diffs, FileDiff{
|
||||
Name: file,
|
||||
Content: string(diffOut),
|
||||
})
|
||||
}
|
||||
return diffs
|
||||
}
|
||||
|
||||
// getAvailableYears scans all repositories to find years that have commit activity matching the user's identity.
|
||||
func getAvailableYears(root string, authorPatterns []string) []int {
|
||||
yearsMap := make(map[int]bool)
|
||||
var mu sync.Mutex
|
||||
var wg sync.WaitGroup
|
||||
|
||||
entries, err := os.ReadDir(root)
|
||||
if err != nil {
|
||||
return []int{time.Now().Year()}
|
||||
}
|
||||
|
||||
sem := make(chan struct{}, 20)
|
||||
|
||||
for _, e := range entries {
|
||||
if e.IsDir() {
|
||||
wg.Add(1)
|
||||
go func(e os.DirEntry) {
|
||||
defer wg.Done()
|
||||
sem <- struct{}{}
|
||||
defer func() { <-sem }()
|
||||
|
||||
repoPath := filepath.Join(root, e.Name())
|
||||
if _, err := os.Stat(filepath.Join(repoPath, ".git")); err == nil {
|
||||
args := []string{"-C", repoPath, "log", "--all", "--format=%ad", "--date=format:%Y"}
|
||||
for _, author := range authorPatterns {
|
||||
if author != "" {
|
||||
args = append(args, "--author="+author)
|
||||
}
|
||||
}
|
||||
|
||||
cmd := exec.Command("git", args...)
|
||||
out, err := cmd.Output()
|
||||
if err == nil {
|
||||
localYears := make(map[int]bool)
|
||||
scanner := bufio.NewScanner(bytes.NewReader(out))
|
||||
for scanner.Scan() {
|
||||
yStr := strings.TrimSpace(scanner.Text())
|
||||
if y, err := strconv.Atoi(yStr); err == nil {
|
||||
localYears[y] = true
|
||||
}
|
||||
}
|
||||
|
||||
mu.Lock()
|
||||
for y := range localYears {
|
||||
yearsMap[y] = true
|
||||
}
|
||||
mu.Unlock()
|
||||
}
|
||||
}
|
||||
}(e)
|
||||
}
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
var years []int
|
||||
for y := range yearsMap {
|
||||
years = append(years, y)
|
||||
}
|
||||
if len(years) == 0 {
|
||||
years = append(years, time.Now().Year())
|
||||
}
|
||||
sort.Sort(sort.Reverse(sort.IntSlice(years)))
|
||||
return years
|
||||
}
|
||||
|
||||
// scanCommitsForYear aggregates commit activity for a given year across all repositories.
|
||||
// It returns data suitable for populating the contribution graph and the activity feed.
|
||||
func scanCommitsForYear(root string, year int, authorPatterns []string) ([]ContributionDay, []ActivityMonth) {
|
||||
commitCounts := make(map[string]int)
|
||||
repoMonthlyActivity := make(map[string]map[string]int)
|
||||
|
||||
var mu sync.Mutex
|
||||
var wg sync.WaitGroup
|
||||
|
||||
entries, err := os.ReadDir(root)
|
||||
if err != nil {
|
||||
return []ContributionDay{}, []ActivityMonth{}
|
||||
}
|
||||
|
||||
sem := make(chan struct{}, 20)
|
||||
|
||||
for _, e := range entries {
|
||||
if e.IsDir() {
|
||||
wg.Add(1)
|
||||
go func(e os.DirEntry) {
|
||||
defer wg.Done()
|
||||
sem <- struct{}{}
|
||||
defer func() { <-sem }()
|
||||
|
||||
repoPath := filepath.Join(root, e.Name())
|
||||
if _, err := os.Stat(filepath.Join(repoPath, ".git")); err == nil {
|
||||
args := []string{"-C", repoPath, "log", "--all", "--format=%ad", "--date=short"}
|
||||
for _, author := range authorPatterns {
|
||||
if author != "" {
|
||||
args = append(args, "--author="+author)
|
||||
}
|
||||
}
|
||||
|
||||
cmd := exec.Command("git", args...)
|
||||
out, err := cmd.Output()
|
||||
if err == nil {
|
||||
localCounts := make(map[string]int)
|
||||
localMonthly := make(map[string]int)
|
||||
|
||||
scanner := bufio.NewScanner(bytes.NewReader(out))
|
||||
for scanner.Scan() {
|
||||
date := strings.TrimSpace(scanner.Text())
|
||||
if strings.HasPrefix(date, fmt.Sprintf("%d-", year)) {
|
||||
localCounts[date]++
|
||||
|
||||
t, _ := time.Parse("2006-01-02", date)
|
||||
monthKey := t.Format("January 2006")
|
||||
localMonthly[monthKey]++
|
||||
}
|
||||
}
|
||||
|
||||
mu.Lock()
|
||||
for d, c := range localCounts {
|
||||
commitCounts[d] += c
|
||||
}
|
||||
for m, c := range localMonthly {
|
||||
if repoMonthlyActivity[m] == nil {
|
||||
repoMonthlyActivity[m] = make(map[string]int)
|
||||
}
|
||||
repoMonthlyActivity[m][e.Name()] += c
|
||||
}
|
||||
mu.Unlock()
|
||||
}
|
||||
}
|
||||
}(e)
|
||||
}
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
wg.Wait()
|
||||
|
||||
// Initialize the contribution graph grid, accounting for leap years and weekly offset.
|
||||
var graph []ContributionDay
|
||||
startDate := time.Date(year, 1, 1, 0, 0, 0, 0, time.UTC)
|
||||
endDate := time.Date(year, 12, 31, 0, 0, 0, 0, time.UTC)
|
||||
|
||||
offset := int(startDate.Weekday())
|
||||
for i := 0; i < offset; i++ {
|
||||
graph = append(graph, ContributionDay{
|
||||
Date: "",
|
||||
Count: 0,
|
||||
Color: "contrib-empty",
|
||||
})
|
||||
}
|
||||
|
||||
for d := startDate; !d.After(endDate); d = d.AddDate(0, 0, 1) {
|
||||
// Calculate the contribution level (color intensity) based on the number of commits for the day.
|
||||
dateStr := d.Format("2006-01-02")
|
||||
count := commitCounts[dateStr]
|
||||
|
||||
level := 0
|
||||
if count > 0 {
|
||||
level = 1
|
||||
}
|
||||
if count >= 3 {
|
||||
level = 2
|
||||
}
|
||||
if count >= 6 {
|
||||
level = 3
|
||||
}
|
||||
if count >= 10 {
|
||||
level = 4
|
||||
}
|
||||
|
||||
colorClass := fmt.Sprintf("contrib-level-%d", level)
|
||||
|
||||
graph = append(graph, ContributionDay{
|
||||
Date: dateStr,
|
||||
Count: count,
|
||||
Color: colorClass,
|
||||
})
|
||||
}
|
||||
|
||||
var activityFeed []ActivityMonth
|
||||
|
||||
for m := 12; m >= 1; m-- {
|
||||
t := time.Date(year, time.Month(m), 1, 0, 0, 0, 0, time.UTC)
|
||||
monthKey := t.Format("January 2006")
|
||||
|
||||
if repos, ok := repoMonthlyActivity[monthKey]; ok {
|
||||
var items []ActivityItem
|
||||
totalCommitsInMonth := 0
|
||||
for repoName, count := range repos {
|
||||
items = append(items, ActivityItem{
|
||||
RepoName: repoName,
|
||||
Commits: count,
|
||||
})
|
||||
totalCommitsInMonth += count
|
||||
}
|
||||
sort.Slice(items, func(i, j int) bool {
|
||||
return items[i].Commits > items[j].Commits
|
||||
})
|
||||
|
||||
activityFeed = append(activityFeed, ActivityMonth{
|
||||
MonthName: monthKey,
|
||||
YearInt: year,
|
||||
MonthInt: m,
|
||||
TotalCommits: totalCommitsInMonth,
|
||||
RepoCount: len(items),
|
||||
Items: items,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return graph, activityFeed
|
||||
}
|
||||
|
||||
// scanActivityForDate retrieves detailed activity (list of repositories and commit counts) for a specific date.
|
||||
func scanActivityForDate(scanPath string, dateStr string, authorPatterns []string) []ActivityMonth {
|
||||
var activityFeed []ActivityMonth
|
||||
|
||||
t, err := time.Parse("2006-01-02", dateStr)
|
||||
if err != nil {
|
||||
return activityFeed
|
||||
}
|
||||
|
||||
entries, err := os.ReadDir(scanPath)
|
||||
if err != nil {
|
||||
return activityFeed
|
||||
}
|
||||
|
||||
reposMap := make(map[string]int)
|
||||
var mu sync.Mutex
|
||||
var wg sync.WaitGroup
|
||||
sem := make(chan struct{}, 20)
|
||||
|
||||
for _, e := range entries {
|
||||
if e.IsDir() {
|
||||
wg.Add(1)
|
||||
go func(repoName string) {
|
||||
defer wg.Done()
|
||||
sem <- struct{}{}
|
||||
defer func() { <-sem }()
|
||||
|
||||
repoPath := filepath.Join(scanPath, repoName)
|
||||
if _, err := os.Stat(filepath.Join(repoPath, ".git")); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
args := []string{"-C", repoPath, "log", "--all", "--format=%an|%ae|%ad", "--date=short"}
|
||||
|
||||
cmd := exec.Command("git", args...)
|
||||
out, err := cmd.Output()
|
||||
if err == nil && len(out) > 0 {
|
||||
scanner := bufio.NewScanner(bytes.NewReader(out))
|
||||
count := 0
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
parts := strings.Split(line, "|")
|
||||
if len(parts) < 3 {
|
||||
continue
|
||||
}
|
||||
name := strings.TrimSpace(parts[0])
|
||||
email := strings.TrimSpace(parts[1])
|
||||
date := strings.TrimSpace(parts[2])
|
||||
|
||||
if date != dateStr {
|
||||
continue
|
||||
}
|
||||
|
||||
match := false
|
||||
if len(authorPatterns) == 0 {
|
||||
match = true
|
||||
} else {
|
||||
for _, p := range authorPatterns {
|
||||
if strings.EqualFold(p, name) || strings.EqualFold(p, email) {
|
||||
match = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if match {
|
||||
count++
|
||||
}
|
||||
}
|
||||
|
||||
if count > 0 {
|
||||
mu.Lock()
|
||||
reposMap[repoName] = count
|
||||
mu.Unlock()
|
||||
}
|
||||
}
|
||||
}(e.Name())
|
||||
}
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
if len(reposMap) > 0 {
|
||||
var items []ActivityItem
|
||||
total := 0
|
||||
for name, count := range reposMap {
|
||||
items = append(items, ActivityItem{RepoName: name, Commits: count})
|
||||
total += count
|
||||
}
|
||||
sort.Slice(items, func(i, j int) bool {
|
||||
return items[i].Commits > items[j].Commits
|
||||
})
|
||||
|
||||
title := fmt.Sprintf("Activity on %s", t.Format("Jan 02, 2006"))
|
||||
|
||||
activityFeed = append(activityFeed, ActivityMonth{
|
||||
MonthName: title,
|
||||
TotalCommits: total,
|
||||
RepoCount: len(items),
|
||||
Items: items,
|
||||
YearInt: t.Year(),
|
||||
MonthInt: int(t.Month()),
|
||||
Date: dateStr,
|
||||
})
|
||||
}
|
||||
|
||||
return activityFeed
|
||||
}
|
||||
|
||||
// getCommitsForDate retrieves all commits made on a specific date in a given repository.
|
||||
func getCommitsForDate(repoPath string, dateStr string) []Commit {
|
||||
var commits []Commit
|
||||
|
||||
args := []string{"-C", repoPath, "log", "--all", "--format=%h|%s|%an|%ar|%ad", "--date=short"}
|
||||
|
||||
cmd := exec.Command("git", args...)
|
||||
out, err := cmd.Output()
|
||||
if err != nil {
|
||||
return commits
|
||||
}
|
||||
|
||||
scanner := bufio.NewScanner(bytes.NewReader(out))
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
parts := strings.Split(line, "|")
|
||||
if len(parts) < 5 {
|
||||
continue
|
||||
}
|
||||
|
||||
cDate := strings.TrimSpace(parts[4])
|
||||
|
||||
if cDate == dateStr {
|
||||
commits = append(commits, Commit{
|
||||
Hash: parts[0],
|
||||
Message: parts[1],
|
||||
Author: parts[2],
|
||||
Date: parts[3],
|
||||
})
|
||||
}
|
||||
}
|
||||
return commits
|
||||
}
|
||||
Loading…
Reference in New Issue