Skip to content

Commit 78b0899

Browse files
committed
initial commit
0 parents  commit 78b0899

File tree

7 files changed

+254
-0
lines changed

7 files changed

+254
-0
lines changed

.gitignore

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
vendor
2+
go.sum
3+
git-log-exec
4+
out.csv
5+
.idea

Makefile

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
2+
go-log-exec:
3+
go build -o go-log-exec *.go

git.go

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
package main
2+
3+
import (
4+
"fmt"
5+
"strconv"
6+
"strings"
7+
)
8+
9+
func getCommits(branch string, limit int, after string, before string) ([]entry, error) {
10+
checkout(branch)
11+
12+
args := []string{
13+
"log",
14+
"--pretty=\"%h %ct\"",
15+
}
16+
if after != "" {
17+
args = append(args, fmt.Sprintf("--from='%s'", after))
18+
}
19+
if before != "" {
20+
args = append(args, fmt.Sprintf("--before='%s'", before))
21+
}
22+
23+
stdout, err := execute("git", args...)
24+
25+
commits := make([]entry, 0)
26+
if err != nil {
27+
return commits, err
28+
}
29+
30+
lines := strings.Split(stdout, "\n")
31+
32+
stepSize := (len(lines) / limit) - 1
33+
if stepSize == 0 {
34+
stepSize = 1
35+
}
36+
37+
for i, line := range lines {
38+
if line == "" {
39+
continue
40+
}
41+
if i%stepSize != 0 {
42+
continue
43+
}
44+
parts := strings.Split(strings.ReplaceAll(line, "\"", ""), " ")
45+
timestamp, _ := strconv.Atoi(parts[1])
46+
commits = append(commits, entry{
47+
commit: parts[0],
48+
timestamp: timestamp,
49+
})
50+
}
51+
52+
fmt.Printf("Commits: %d, Step size: %d (%d commits to check)\n", len(lines), stepSize, len(commits))
53+
54+
return commits, nil
55+
}
56+
57+
func checkout(branch string) error {
58+
_, err := execute("git", "checkout", branch)
59+
60+
return err
61+
}

go.mod

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
module github.com/brainexe/git-log-exec
2+
3+
go 1.13
4+
5+
require github.com/schollz/progressbar/v2 v2.15.0

main.go

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
package main
2+
3+
import (
4+
"flag"
5+
"fmt"
6+
"os"
7+
"os/exec"
8+
"strings"
9+
10+
"github.com/schollz/progressbar/v2"
11+
)
12+
13+
type entry struct {
14+
commit string
15+
timestamp int
16+
data string
17+
}
18+
19+
var directory string
20+
var command string
21+
var branch string
22+
var outputFile string
23+
var limit int
24+
var after string
25+
var before string
26+
27+
func main() {
28+
flag.StringVar(&directory, "directory", "", "git workspace (by default current directory)")
29+
flag.StringVar(&command, "command", "", "command to execute")
30+
flag.StringVar(&branch, "branch", "master", "git branch to check")
31+
flag.StringVar(&outputFile, "output", "out.csv", "output csv file")
32+
flag.StringVar(&after, "after", "", "optional begin date of history search")
33+
flag.StringVar(&before, "before", "", "optional end date of history search")
34+
flag.IntVar(&limit, "limit", 500, "max number of commits to check")
35+
flag.Parse()
36+
37+
// if no parameter given, use history of current history
38+
var err error
39+
if directory == "" {
40+
directory, err = os.Getwd()
41+
checkError(err)
42+
}
43+
44+
if stat, err := os.Stat(directory); err != nil || !stat.IsDir() {
45+
fmt.Printf("Invalid directory: %s\n", directory)
46+
flag.PrintDefaults()
47+
os.Exit(1)
48+
}
49+
50+
commits, err := getCommits(branch, limit, after, before)
51+
checkError(err)
52+
53+
bar := progressbar.New(len(commits))
54+
result := make([]entry, 0, len(commits))
55+
56+
for _, commit := range commits {
57+
bar.Add(1)
58+
result = append(result, evaluate(commit))
59+
}
60+
61+
// checkout to initial version
62+
checkout(branch)
63+
64+
out, err := os.Create(outputFile)
65+
checkError(err)
66+
defer out.Close()
67+
68+
err = writeCsv(result, out)
69+
checkError(err)
70+
fmt.Printf("\nWrote file %s\n", outputFile)
71+
}
72+
73+
func checkError(err error) {
74+
if err != nil {
75+
fmt.Println(err)
76+
os.Exit(1)
77+
}
78+
}
79+
80+
func evaluate(commit entry) entry {
81+
checkout(commit.commit)
82+
83+
stdout, _ := execute("sh", "-c", command)
84+
85+
commit.data = strings.TrimSpace(stdout)
86+
87+
return commit
88+
}
89+
90+
func execute(name string, args ...string) (string, error) {
91+
cmd := exec.Command(name, args...)
92+
cmd.Dir = directory
93+
out, err := cmd.CombinedOutput()
94+
95+
if err != nil {
96+
fmt.Fprintf(os.Stderr, "Command '%s' failed: %s: %s", cmd.String(), err, string(out))
97+
os.Exit(1)
98+
}
99+
100+
return string(out), err
101+
}

readme.md

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
# git-log-exec
2+
Executes any bash command on the whole git history and produces a CSV file out of it.
3+
4+
**Usecases:**
5+
- LOC/files over time
6+
- trend of build/compile time
7+
- trend of technical dept (e.g. search for certain method calls)
8+
9+
## Example
10+
```
11+
cd ~/projects/home-assistant
12+
git-log-exec -out loc.csv -command="find homeassistant -type f | wc -l"
13+
```
14+
15+
**Result**
16+
```
17+
time,result,commit
18+
2019-12-22T23:41:22+01:00,12064,48d35a455
19+
2019-12-04T00:46:38+01:00,12052,564c468c2
20+
2019-11-27T20:52:03+01:00,12006,d7a66e6e4
21+
2019-11-25T04:57:40+01:00,11876,c38240673
22+
2019-11-13T15:32:22+01:00,11876,15ce73835
23+
2019-11-03T20:36:02+01:00,11779,5fd9b474d
24+
2019-10-25T01:42:54+02:00,11666,643b3a98e
25+
2019-10-21T09:55:53+02:00,11607,c1fccee83
26+
2019-10-17T15:03:05+02:00,11551,8350e1246
27+
2019-10-12T21:57:18+02:00,11486,17b1ba2e9
28+
2019-10-07T21:49:54+02:00,11420,1febb32dd
29+
```
30+
31+
## Options
32+
```
33+
-command string
34+
command to execute
35+
-directory string
36+
git workspace (by default current directory)
37+
-limit int
38+
max number of commits to check (default 500)
39+
-branch string
40+
git branch to check (default "master")
41+
-output string
42+
output csv file (default "out.csv")
43+
-after string
44+
optional begin date of history search
45+
-before string
46+
optional end date of history search
47+
```

writer.go

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package main
2+
3+
import (
4+
"encoding/csv"
5+
"io"
6+
"time"
7+
)
8+
9+
func writeCsv(logs []entry, file io.Writer) error {
10+
// todo sort by time
11+
12+
csvwriter := csv.NewWriter(file)
13+
csvwriter.Write([]string{
14+
"time",
15+
"result",
16+
"commit",
17+
})
18+
for _, row := range logs {
19+
err := csvwriter.Write([]string{
20+
time.Unix(int64(row.timestamp), 0).Format(time.RFC3339),
21+
row.data,
22+
row.commit,
23+
})
24+
if err != nil {
25+
return err
26+
}
27+
}
28+
29+
csvwriter.Flush()
30+
31+
return nil
32+
}

0 commit comments

Comments
 (0)