Skip to content

Commit a80e3d5

Browse files
committed
feat: optimize repository generation using git fast-import for improved performance
1 parent b50c1bb commit a80e3d5

File tree

1 file changed

+95
-48
lines changed

1 file changed

+95
-48
lines changed

app.go

Lines changed: 95 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,16 @@
11
package main
22

33
import (
4-
"bytes"
5-
"context"
6-
"fmt"
7-
"os"
8-
"os/exec"
9-
"path/filepath"
10-
"regexp"
11-
"strings"
12-
"time"
4+
"bytes"
5+
"context"
6+
"fmt"
7+
"os"
8+
"os/exec"
9+
"path/filepath"
10+
"regexp"
11+
"sort"
12+
"strings"
13+
"time"
1314
)
1415

1516
// App struct
@@ -119,45 +120,77 @@ func (a *App) GenerateRepo(req GenerateRepoRequest) (*GenerateRepoResponse, erro
119120
return nil, err
120121
}
121122

122-
activityFileName := "activity.log"
123-
activityFilePath := filepath.Join(repoPath, activityFileName)
124-
125-
totalCommits := 0
126-
for _, day := range req.Contributions {
127-
if day.Count <= 0 {
128-
continue
129-
}
130-
parsedDate, err := time.Parse("2006-01-02", day.Date)
131-
if err != nil {
132-
return nil, fmt.Errorf("invalid date %q: %w", day.Date, err)
133-
}
134-
135-
for i := 0; i < day.Count; i++ {
136-
entry := fmt.Sprintf("%s commit %d\n", day.Date, i+1)
137-
if err := appendToFile(activityFilePath, entry); err != nil {
138-
return nil, fmt.Errorf("update activity log: %w", err)
139-
}
140-
141-
if err := runGitCommand(repoPath, "add", activityFileName, filepath.Base(readmePath)); err != nil {
142-
return nil, err
143-
}
144-
145-
commitTime := parsedDate.Add(time.Duration(i) * time.Second)
146-
env := map[string]string{
147-
"GIT_AUTHOR_NAME": username,
148-
"GIT_AUTHOR_EMAIL": email,
149-
"GIT_COMMITTER_NAME": username,
150-
"GIT_COMMITTER_EMAIL": email,
151-
"GIT_AUTHOR_DATE": commitTime.Format(time.RFC3339),
152-
"GIT_COMMITTER_DATE": commitTime.Format(time.RFC3339),
153-
}
154-
message := fmt.Sprintf("Contribution on %s (%d/%d)", day.Date, i+1, day.Count)
155-
if err := runGitCommandWithEnv(repoPath, env, "commit", "-m", message); err != nil {
156-
return nil, err
157-
}
158-
totalCommits++
159-
}
160-
}
123+
// Optimize: use git fast-import to avoid spawning a process per commit.
124+
// Also disable slow features for this repo.
125+
_ = runGitCommand(repoPath, "config", "commit.gpgsign", "false")
126+
_ = runGitCommand(repoPath, "config", "gc.auto", "0")
127+
_ = runGitCommand(repoPath, "config", "core.autocrlf", "false")
128+
_ = runGitCommand(repoPath, "config", "core.fsyncObjectFiles", "false")
129+
130+
// Sort contributions by date ascending to produce chronological history
131+
contribs := make([]ContributionDay, 0, len(req.Contributions))
132+
for _, c := range req.Contributions {
133+
if c.Count > 0 {
134+
contribs = append(contribs, c)
135+
}
136+
}
137+
sort.Slice(contribs, func(i, j int) bool { return contribs[i].Date < contribs[j].Date })
138+
139+
// Build fast-import stream
140+
var stream bytes.Buffer
141+
// Create README blob once and mark it
142+
fmt.Fprintf(&stream, "blob\nmark :1\n")
143+
fmt.Fprintf(&stream, "data %d\n%s\n", len(readmeContent), readmeContent)
144+
145+
// Prepare to accumulate activity log content across commits
146+
var activityBuf bytes.Buffer
147+
nextMark := 2
148+
totalCommits := 0
149+
branch := "refs/heads/main"
150+
151+
for _, day := range contribs {
152+
parsedDate, err := time.Parse("2006-01-02", day.Date)
153+
if err != nil {
154+
return nil, fmt.Errorf("invalid date %q: %w", day.Date, err)
155+
}
156+
for i := 0; i < day.Count; i++ {
157+
// Update activity content in-memory
158+
entry := fmt.Sprintf("%s commit %d\n", day.Date, i+1)
159+
activityBuf.WriteString(entry)
160+
161+
// Emit blob for activity.log
162+
fmt.Fprintf(&stream, "blob\nmark :%d\n", nextMark)
163+
act := activityBuf.Bytes()
164+
fmt.Fprintf(&stream, "data %d\n", len(act))
165+
stream.Write(act)
166+
stream.WriteString("\n")
167+
168+
// Emit commit that points to README (:1) and activity (:nextMark)
169+
commitTime := parsedDate.Add(time.Duration(i) * time.Second)
170+
secs := commitTime.Unix()
171+
tz := commitTime.Format("-0700")
172+
msg := fmt.Sprintf("Contribution on %s (%d/%d)", day.Date, i+1, day.Count)
173+
fmt.Fprintf(&stream, "commit %s\n", branch)
174+
fmt.Fprintf(&stream, "author %s <%s> %d %s\n", username, email, secs, tz)
175+
fmt.Fprintf(&stream, "committer %s <%s> %d %s\n", username, email, secs, tz)
176+
fmt.Fprintf(&stream, "data %d\n%s\n", len(msg), msg)
177+
fmt.Fprintf(&stream, "M 100644 :1 %s\n", filepath.Base(readmePath))
178+
fmt.Fprintf(&stream, "M 100644 :%d activity.log\n", nextMark)
179+
180+
nextMark++
181+
totalCommits++
182+
}
183+
}
184+
stream.WriteString("done\n")
185+
186+
// Feed stream to fast-import
187+
if totalCommits > 0 {
188+
if err := runGitFastImport(repoPath, &stream); err != nil {
189+
return nil, fmt.Errorf("fast-import failed: %w", err)
190+
}
191+
// Update working tree to the generated branch for user convenience
192+
_ = runGitCommand(repoPath, "checkout", "-f", "main")
193+
}
161194

162195
if err := openDirectory(repoPath); err != nil {
163196
return nil, fmt.Errorf("open repo directory: %w", err)
@@ -233,3 +266,17 @@ func runGitCommandWithEnv(dir string, extraEnv map[string]string, args ...string
233266

234267
return nil
235268
}
269+
270+
// runGitFastImport runs `git fast-import` with the given stream as stdin.
271+
func runGitFastImport(dir string, r *bytes.Buffer) error {
272+
cmd := exec.Command("git", "fast-import", "--quiet")
273+
cmd.Dir = dir
274+
configureCommand(cmd, true)
275+
cmd.Stdin = r
276+
var stderr bytes.Buffer
277+
cmd.Stderr = &stderr
278+
if err := cmd.Run(); err != nil {
279+
return fmt.Errorf("git fast-import: %w (%s)", err, strings.TrimSpace(stderr.String()))
280+
}
281+
return nil
282+
}

0 commit comments

Comments
 (0)