未验证 提交 b0ebba13 编写于 作者: P Phodal Huang

feat: add gitt code

上级 d825e524
......@@ -17,10 +17,13 @@ Usage:
Available Commands:
analysis analysis package
api scan api
bs Bad Code Smell
call call graph api
concept concept api
help Help about any command
refactor auto refactor code
scc scc [FILE or DIRECTORY]
```
......
package cmd
import (
. "coca/src/gitt"
"fmt"
"github.com/olekukonko/tablewriter"
"github.com/spf13/cobra"
"io/ioutil"
"os"
"strconv"
)
var (
relatedConfig string
)
var gitCmd *cobra.Command = &cobra.Command{
Use: "ga",
Short: "git analysis",
Long: ``,
Run: func(cmd *cobra.Command, args []string) {
message := BuildCommitMessage()
isFullMessage := cmd.Flag("basic").Value.String() == "true"
if cmd.Flag("basic").Value.String() == "true" {
table := tablewriter.NewWriter(os.Stdout)
basicSummary := BasicSummary(message)
table.SetHeader([]string{"Statistic", "Number"})
table.Append([]string{"Commits", strconv.Itoa(basicSummary.Commits)})
table.Append([]string{"Entities", strconv.Itoa(basicSummary.Entities)})
table.Append([]string{"Changes", strconv.Itoa(basicSummary.Changes)})
table.Append([]string{"Authors", strconv.Itoa(basicSummary.Authors)})
table.Render()
}
if cmd.Flag("team").Value.String() == "true" {
table := tablewriter.NewWriter(os.Stdout)
teamSummary := GetTeamSummary(message)
table.SetHeader([]string{"EntityName", "RevsCount", "AuthorCount"})
if len(teamSummary) > 20 && isFullMessage {
teamSummary = teamSummary[:20]
}
for _, v := range teamSummary {
table.Append([]string{v.EntityName, strconv.Itoa(v.RevsCount), strconv.Itoa(v.AuthorCount)})
}
table.Render()
}
if cmd.Flag("age").Value.String() == "true" {
table := tablewriter.NewWriter(os.Stdout)
age := CalculateCodeAge(message)
table.SetHeader([]string{"File", "Month"})
if len(age) > 20 && isFullMessage {
age = age[:20]
}
for _, v := range age {
table.Append([]string{v.File, v.Month})
}
table.Render()
}
if cmd.Flag("top").Value.String() == "true" {
table := tablewriter.NewWriter(os.Stdout)
authors := GetTopAuthors(message)
table.SetHeader([]string{"Author", "CommitCount", "LineCount"})
if len(authors) > 20 && isFullMessage {
authors = authors[:20]
}
for _, v := range authors {
table.Append([]string{v.Name, strconv.Itoa(v.CommitCount), strconv.Itoa(v.LineCount)})
}
table.Render()
}
if relatedConfig != "" {
config, err := ioutil.ReadFile(relatedConfig)
if err != nil {
_ = fmt.Errorf("🥯 🦆 🦉 🥓 🦄 🦀 🖕 🍣 🍤 🍥 , lost related json", err)
return
}
GetRelatedFiles(message, config)
//results := GetRelatedFiles(message, config)
//fmt.Println(results)
}
},
}
func init() {
rootCmd.AddCommand(gitCmd)
gitCmd.PersistentFlags().BoolP("basic", "b", false, "Basic Summary")
gitCmd.PersistentFlags().BoolP("team", "t", false, "Team Summary")
gitCmd.PersistentFlags().BoolP("age", "a", false, "Code Age")
gitCmd.PersistentFlags().BoolP("top", "o", false, "Top Authors")
gitCmd.PersistentFlags().BoolP("full", "f", false, "full")
gitCmd.PersistentFlags().StringVar(&relatedConfig, "r", "", "related")
}
......@@ -9,6 +9,7 @@ require (
github.com/iancoleman/strcase v0.0.0-20191112232945-16388991a334
github.com/minio/blake2b-simd v0.0.0-20160723061019-3f5f724cb5b1 // indirect
github.com/monochromegane/go-gitignore v0.0.0-20160105113617-38717d0a108c // indirect
github.com/olekukonko/tablewriter v0.0.4
github.com/onsi/ginkgo v1.10.3
github.com/onsi/gomega v1.7.1
github.com/spf13/cobra v0.0.5
......
......@@ -26,12 +26,16 @@ github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NH
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/karrick/godirwalk v1.10.12/go.mod h1:RoGL9dQei4vP9ilrpETWE8CLOZ1kiN0LhBygSwrAsHA=
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/mattn/go-runewidth v0.0.7 h1:Ei8KR0497xHyKJPAv59M1dkC+rOZCMBJ+t3fZ+twI54=
github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/minio/blake2b-simd v0.0.0-20160723061019-3f5f724cb5b1 h1:lYpkrQH5ajf0OXOcUbGjvZxxijuBwbbmlSxLiuofa+g=
github.com/minio/blake2b-simd v0.0.0-20160723061019-3f5f724cb5b1/go.mod h1:pD8RvIylQ358TN4wwqatJ8rNavkEINozVn9DtGI3dfQ=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/monochromegane/go-gitignore v0.0.0-20160105113617-38717d0a108c h1:RRUev95N3Gq4Aog4avFzXJA2V8fgXmND+cvcH7KLMyk=
github.com/monochromegane/go-gitignore v0.0.0-20160105113617-38717d0a108c/go.mod h1:Pm3mSP3c5uWn86xMLZ5Sa7JB9GsEZySvHYXCTK4E9q4=
github.com/olekukonko/tablewriter v0.0.4 h1:vHD/YYe1Wolo78koG299f7V/VAS08c6IpCLn+Ejf/w8=
github.com/olekukonko/tablewriter v0.0.4/go.mod h1:zq6QwlOf5SlnkVbMSr5EoBv3636FWnp+qbPhuoO21uA=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.10.3 h1:OoxbjfXVZyod1fmWYhI7SEyaD8B00ynP3T+D5GiyHOY=
github.com/onsi/ginkgo v1.10.3/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
......
GitLogs
```
git log --all --numstat --date=short --pretty=format:'--%h--%ad--%aN' --no-renames
```
Related Projects: [https://github.com/bast/gitink](https://github.com/bast/gitink)
```
$ cat example.txt
[feature]
|
v
x1-----x2
/
c1----c2----m1----c3----c4
\ / ^
b1----b2----b3 |
^ ^ [master,HEAD]
| |
[_branch] [branch]
$ gitink --time-direction=90 --in-file=example.txt | display
```
// https://github.com/eMAGTechLabs/go-apriori
package apriori
import (
"errors"
"sort"
)
const combinationStringChannelLastElement = "STOP"
const combinationIntChannelLastElement = -1
const minLengthNeededForNextCandidates = 3
type SupportRecord struct {
items []string
support float64
}
func (sr SupportRecord) GetItems() []string {
return sr.items
}
func (sr SupportRecord) GetSupport() float64 {
return sr.support
}
type OrderedStatistic struct {
base []string
add []string
confidence float64
lift float64
}
func (os OrderedStatistic) GetBase() []string {
return os.base
}
func (os OrderedStatistic) GetAdd() []string {
return os.add
}
func (os OrderedStatistic) GetConfidence() float64 {
return os.confidence
}
func (os OrderedStatistic) GetLift() float64 {
return os.lift
}
type RelationRecord struct {
supportRecord SupportRecord
orderedStatistic []OrderedStatistic
}
func (r RelationRecord) GetSupportRecord() SupportRecord {
return r.supportRecord
}
func (r RelationRecord) GetOrderedStatistic() []OrderedStatistic {
return r.orderedStatistic
}
type Options struct {
MinSupport float64 `json:"minSupport"` // The minimum support of relations (float).
MinConfidence float64 `json:"minConfidence"` // The minimum confidence of relations (float).
MinLift float64 `json:"minLift"` // The minimum lift of relations (float).
MaxLength int `json:"maxLength"` // The maximum length of the relation (integer).
}
func (options Options) check() error {
// Check Options
if options.MinSupport <= 0 {
return errors.New("minimum support must be > 0")
}
return nil
}
type Apriori struct {
transactionNo int64
items []string
transactionIndexMap map[interface{}][]int64
}
func NewOptions(minSupport float64, minConfidence float64, minLift float64, maxLength int) Options {
return Options{MinSupport: minSupport, MinConfidence: minConfidence, MinLift: minLift, MaxLength: maxLength}
}
func NewApriori(transactions [][]string) *Apriori {
var a Apriori
a.transactionIndexMap = make(map[interface{}][]int64)
for _, transaction := range transactions {
a.addTransaction(transaction)
}
return &a
}
func (a *Apriori) Calculate(options Options) []RelationRecord {
if err := options.check(); err != nil {
panic(err)
}
// Calculate supports
supportRecords := make(chan SupportRecord)
go a.generateSupportRecords(supportRecords, options.MinSupport, options.MaxLength)
var relationRecords []RelationRecord
// Calculate ordered stats
for {
supportRecord := <-supportRecords
if supportRecord.support == -1 {
break
}
filteredOrderedStatistics := a.filterOrderedStatistics(
a.generateOrderedStatistics(supportRecord),
options.MinConfidence,
options.MinLift)
if len(filteredOrderedStatistics) == 0 {
continue
}
relationRecords = append(relationRecords, RelationRecord{supportRecord, filteredOrderedStatistics})
}
return relationRecords
}
func (a *Apriori) addTransaction(transaction []string) {
for _, item := range transaction {
if _, ok := a.transactionIndexMap[item]; !ok {
a.items = append(a.items, item)
a.transactionIndexMap[item] = []int64{}
}
a.transactionIndexMap[item] = append(a.transactionIndexMap[item], a.transactionNo)
}
a.transactionNo++
}
// Returns a support for items.
func (a *Apriori) calculateSupport(items []string) float64 {
// Empty items are supported by all transactions.
if len(items) == 0 {
return 1.0
}
// Empty transactions supports no items.
if a.transactionNo == 0 {
return 0.0
}
// Create the transaction index intersection.
var sumIndexes []int64
for _, item := range items {
indexes := a.transactionIndexMap[item]
// No support for any set that contains a not existing item.
if len(indexes) == 0 {
return 0.0
}
if len(sumIndexes) == 0 {
// Assign the indexes on the first time.
sumIndexes = indexes
} else {
// Calculate the intersection on not the first time.
sumIndexes = a.transactionIntersection(sumIndexes, indexes)
}
}
// Calculate and return the support.
return float64(len(sumIndexes)) / float64(a.transactionNo)
}
// Returns the initial candidates.
func (a *Apriori) initialCandidates() [][]string {
var initialCandidates [][]string
for _, item := range a.getItems() {
initialCandidates = append(initialCandidates, []string{item})
}
return initialCandidates
}
// Returns the item list that the transaction is consisted of.
func (a *Apriori) getItems() []string {
sort.Strings(a.items)
return a.items
}
// Returns a generator of ordered statistics as OrderedStatistic instances.
func (a *Apriori) generateOrderedStatistics(record SupportRecord) []OrderedStatistic {
items := record.items
sort.Strings(items)
var ch = make(chan []string)
defer close(ch)
go combinations(ch, items, len(items)-1)
var orderedStatistics []OrderedStatistic
for combination := range ch {
if checkIfLastInStringChan(combination) {
break
}
orderedStatistics = append(orderedStatistics, a.generateOrderedStatistic(combination, items, record.support))
}
return orderedStatistics
}
func (a *Apriori) generateOrderedStatistic(base []string, items []string, recordSupport float64) OrderedStatistic {
add := a.itemDifference(items, base)
supportForBase := a.calculateSupport(base)
confidence := recordSupport / supportForBase
supportForAdd := a.calculateSupport(add)
lift := confidence / supportForAdd
return OrderedStatistic{base, add, confidence, lift}
}
// Filter OrderedStatistic objects
func (a *Apriori) filterOrderedStatistics(orderedStatistics []OrderedStatistic, minConfidence float64, minLift float64) []OrderedStatistic {
var filteredOrderedStatistic []OrderedStatistic
for _, orderedStatistic := range orderedStatistics {
if orderedStatistic.confidence < minConfidence || orderedStatistic.lift < minLift {
continue
}
filteredOrderedStatistic = append(filteredOrderedStatistic, orderedStatistic)
}
return filteredOrderedStatistic
}
// Returns a generator of support records with given transactions.
func (a *Apriori) generateSupportRecords(supportRecordChan chan SupportRecord, minSupport float64, maxLength int) {
// Process
candidates := a.initialCandidates()
var length = 1
for len(candidates) > 0 {
var relations [][]string
for _, relationCandidate := range candidates {
support := a.calculateSupport(relationCandidate)
if support < minSupport {
continue
}
relations = append(relations, relationCandidate)
supportRecordChan <- SupportRecord{relationCandidate, support}
}
length++
if maxLength != 0 && length > maxLength {
break
}
candidates = a.createNextCandidates(relations, length)
}
supportRecordChan <- SupportRecord{[]string{}, -1}
}
func (a *Apriori) generateRelationRecords(relationRecords chan RelationRecord, supportRecord SupportRecord, minConfidence float64, minLift float64) {
// Calculate ordered stats
filteredOrderedStatistics := a.filterOrderedStatistics(
a.generateOrderedStatistics(supportRecord),
minConfidence,
minLift)
if len(filteredOrderedStatistics) != 0 {
relationRecords <- RelationRecord{supportRecord, filteredOrderedStatistics}
}
}
// Returns the Apriori candidates as a list.
func (a *Apriori) createNextCandidates(prevCandidates [][]string, length int) [][]string {
var items []string
for _, candidate := range prevCandidates {
for _, item := range candidate {
items = append(items, item)
}
}
sort.Strings(items)
items = a.uniqueItems(items)
// Create the temporary candidates. These will be filtered below.
tmpNextCandidates := a.generateCandidateCombinations(items, length)
// Return all the candidates if the length of the next candidates is 2
// because their subsets are the same as items.
if length < minLengthNeededForNextCandidates {
return tmpNextCandidates
}
// Filter candidates that all of their subsets are
// in the previous candidates.
var nextCandidates [][]string
for _, candidate := range tmpNextCandidates {
candidateCombinations := a.generateCandidateCombinations(candidate, length-1)
allAreInPrev := 0
for _, candidates := range candidateCombinations {
if a.isSubset(candidates, prevCandidates) {
allAreInPrev++
}
}
if allAreInPrev == len(candidateCombinations) {
nextCandidates = append(nextCandidates, candidate)
}
}
return nextCandidates
}
func (a *Apriori) generateCandidateCombinations(items []string, length int) [][]string {
var tmpNextCandidates [][]string
if len(items) >= length {
var ch = make(chan []string)
defer close(ch)
go combinations(ch, items, length)
for candidate := range ch {
if checkIfLastInStringChan(candidate) {
break
}
tmpNextCandidates = append(tmpNextCandidates, candidate)
}
}
return tmpNextCandidates
}
func (a *Apriori) isSubset(needle []string, haystack [][]string) bool {
needleLen := len(needle)
for _, value := range haystack {
found := 0
for _, i := range needle {
if a.inSlice(i, value) {
found++
}
}
if needleLen > found {
continue
}
return true
}
return false
}
func (a *Apriori) inSlice(needle string, haystack []string) bool {
for _, str := range haystack {
if str == needle {
return true
}
}
return false
}
func (a *Apriori) uniqueItems(items []string) []string {
keys := make(map[string]bool)
var uniqueItems []string
for _, entry := range items {
if _, value := keys[entry]; !value {
keys[entry] = true
uniqueItems = append(uniqueItems, entry)
}
}
return uniqueItems
}
func (a *Apriori) transactionIntersection(first, second []int64) []int64 {
m := make(map[int64]bool)
var intersection []int64
for _, item := range first {
m[item] = true
}
for _, item := range second {
if _, ok := m[item]; ok {
intersection = append(intersection, item)
}
}
return intersection
}
func (a *Apriori) itemDifference(first []string, second []string) []string {
var diff []string
// Loop two times, first to find first strings not in second,
// second loop to find second strings not in first
for i := 0; i < 2; i++ {
for _, firstString := range first {
found := false
for _, secondString := range second {
if firstString == secondString {
found = true
break
}
}
// String not found. We add it to return slice
if !found {
diff = append(diff, firstString)
}
}
// Swap the slices, only if it was the first loop
if i == 0 {
first, second = second, first
}
}
return diff
}
func combinations(ch chan []string, iterable []string, r int) {
if r != 0 {
length := len(iterable)
if r > length {
panic("Invalid arguments")
}
intCh := make(chan []int)
defer close(intCh)
go genCombinations(intCh, length, r)
for comb := range intCh {
if checkIfLastInIntChan(comb) {
break
}
result := make([]string, r)
for i, val := range comb {
result[i] = iterable[val]
}
ch <- result
}
} else {
result := make([]string, r)
ch <- result
}
ch <- []string{combinationStringChannelLastElement}
}
func genCombinations(ch chan []int, n, r int) {
result := make([]int, r)
for i := range result {
result[i] = i
}
temp := make([]int, r)
copy(temp, result) // avoid overwriting of result
ch <- temp
for {
for i := r - 1; i >= 0; i-- {
if result[i] < i+n-r {
result[i]++
for j := 1; j < r-i; j++ {
result[i+j] = result[i] + j
}
temp := make([]int, r)
copy(temp, result) // avoid overwriting of result
ch <- temp
break
}
}
if result[0] >= n-r {
break
}
}
ch <- []int{combinationIntChannelLastElement}
}
func checkIfLastInStringChan(strings []string) bool {
return len(strings) > 0 && strings[0] == combinationStringChannelLastElement
}
func checkIfLastInIntChan(ints []int) bool {
return len(ints) > 0 && ints[0] == combinationIntChannelLastElement
}
package gitt
import (
"bytes"
. "coca/src/gitt/apriori"
"encoding/json"
"fmt"
"log"
"os/exec"
"regexp"
"sort"
"strconv"
"strings"
"time"
)
var currentCommitMessage CommitMessage
var currentFileChanges []FileChange
var commitMessages []CommitMessage
func BuildCommitMessage() []CommitMessage {
historyArgs := []string{"log", "--pretty=format:[%h] %aN %ad %s", "--date=short", "--numstat"}
cmd := exec.Command("git", historyArgs...)
out, err := cmd.CombinedOutput()
if err != nil {
log.Fatalf("cmd.Run() failed with %s\n", err)
}
splitStr := strings.Split(string(out), "\n");
for _, str := range splitStr {
parseLog(str)
}
return commitMessages
}
func CalculateCodeAge(messages []CommitMessage) []CodeAgeDisplay {
timeFormat := "2006-01-02"
ages := make(map[string]CodeAge)
for _, commitMessage := range messages {
for _, change := range commitMessage.Changes {
if ages[change.File].File == "" {
date, _ := time.Parse(timeFormat, commitMessage.Date)
ages[change.File] = *&CodeAge{change.File, date}
}
}
}
var agesArray []CodeAge
for _, info := range ages {
agesArray = append(agesArray, *&CodeAge{info.File, info.Age})
}
sort.Slice(agesArray, func(i, j int) bool {
return agesArray[i].Age.Before(agesArray[j].Age)
})
var agesDisplay []CodeAgeDisplay
for _, info := range agesArray {
const secondsOfOneMonth = 2600640
month := time.Now().Sub(info.Age).Seconds() / secondsOfOneMonth
displayMonth := strconv.FormatFloat(month, 'f', 2, 64)
agesDisplay = append(agesDisplay, *&CodeAgeDisplay{info.File, displayMonth})
}
return agesDisplay
}
func GetTeamSummary(messages []CommitMessage) []TeamSummary {
infos := make(map[string]TeamInformation)
for _, commitMessage := range messages {
for _, change := range commitMessage.Changes {
if infos[change.File].EntityName == "" {
authors := make(map[string]string)
authors[commitMessage.Author] = commitMessage.Author
revs := make(map[string]string)
revs[commitMessage.Rev] = commitMessage.Rev
infos[change.File] = *&TeamInformation{change.File, authors, revs}
} else {
infos[change.File].Authors[commitMessage.Author] = commitMessage.Author
infos[change.File].Revs[commitMessage.Rev] = commitMessage.Rev
}
}
}
var informations []TeamSummary
for _, info := range infos {
informations = append(informations, *&TeamSummary{info.EntityName, len(info.Authors), len(info.Revs)})
}
sort.Slice(informations, func(i, j int) bool {
return informations[i].RevsCount > informations[j].RevsCount
})
return informations
}
type TopAuthor struct {
Name string
CommitCount int
LineCount int
}
func GetRelatedFiles(commitMessages []CommitMessage, relatedConfig []byte) []RelationRecord {
var dataset [][]string
for _, commitMessage := range commitMessages {
var set []string
for _, change := range commitMessage.Changes {
if strings.HasSuffix(change.File, ".java") && !strings.HasSuffix(change.File, "Test.java") {
if strings.Contains(change.File, "src/main/java/") {
split := strings.Split(change.File, "src/main/java/")
change.File = strings.ReplaceAll(split[1], "/", ".")
}
set = append(set, change.File)
}
}
if len(set) > 2 {
dataset = append(dataset, set)
}
}
var newOptions Options = NewOptions(0.1, 0.9, 0, 0)
decoder := json.NewDecoder(bytes.NewReader(relatedConfig))
decoder.UseNumber()
error := decoder.Decode(&newOptions)
if error != nil {
log.Fatal(error)
return nil
}
fmt.Println(newOptions)
apriori := NewApriori(dataset)
result := apriori.Calculate(newOptions)
for _, res := range result {
items := res.GetSupportRecord().GetItems()
if len(items) > 2 {
fmt.Println(items)
fmt.Println(res.GetSupportRecord().GetSupport())
}
}
return result
}
func GetTopAuthors(commitMessages []CommitMessage) []TopAuthor {
authors := make(map[string]*TopAuthor)
for _, commitMessage := range commitMessages {
if authors[commitMessage.Author] == nil {
authors[commitMessage.Author] = &TopAuthor{commitMessage.Author, 0, 0}
}
authors[commitMessage.Author].CommitCount++
for _, change := range commitMessage.Changes {
authors[commitMessage.Author].LineCount = authors[commitMessage.Author].LineCount + change.Added
authors[commitMessage.Author].LineCount -= change.Deleted
}
}
var topAuthors []TopAuthor
for _, info := range authors {
topAuthors = append(topAuthors, *&TopAuthor{info.Name, info.CommitCount, info.LineCount})
}
sort.Slice(topAuthors, func(i, j int) bool {
return topAuthors[i].CommitCount > topAuthors[j].CommitCount
})
return topAuthors
}
func BasicSummary(commitMessages []CommitMessage) *GitSummary {
authors := make(map[string]string)
entities := make(map[string]string)
commits := len(commitMessages)
changes := 0
for _, commitMessage := range commitMessages {
authors[commitMessage.Author] = commitMessage.Author
for _, change := range commitMessage.Changes {
entities[change.File] = change.File
if change.Added > 0 {
changes++
}
if change.Deleted > 0 {
changes--
}
}
}
authorSummary := len(authors)
entitySummary := len(entities)
gitSummary := &GitSummary{commits, entitySummary, changes, authorSummary}
return gitSummary
}
func parseLog(text string) {
rev := `\[([\d|a-f]{5,8})\]`
author := `(.*?)\s\d{4}-\d{2}-\d{2}`
date := `\d{4}-\d{2}-\d{2}`
// added <tab> deleted <tab> file <nl>
changes := `([\d-])*\t([\d-]*)\t(.*)`
revReg := regexp.MustCompile(rev)
authorReg := regexp.MustCompile(author)
dateReg := regexp.MustCompile(date)
changesReg := regexp.MustCompile(changes)
allString := revReg.FindAllString(text, -1)
if len(allString) == 1 {
str := ""
id := revReg.FindStringSubmatch(text)
str = strings.Split(text, id[0])[1]
auth := authorReg.FindStringSubmatch(str)
str = strings.Split(str, auth[1])[1]
dat := dateReg.FindStringSubmatch(str)
msg := strings.Split(str, dat[0])[1]
msg = msg[1:]
currentCommitMessage = *&CommitMessage{id[1], auth[1], dat[0], msg, nil}
} else if changesReg.MatchString(text) {
changes := changesReg.FindStringSubmatch(text)
deleted, _ := strconv.Atoi(changes[2])
added, _ := strconv.Atoi(changes[1])
change := &FileChange{added, deleted, changes[3]}
currentFileChanges = append(currentFileChanges, *change)
} else {
if currentCommitMessage.Rev != "" {
currentCommitMessage.Changes = currentFileChanges
commitMessages = append(commitMessages, currentCommitMessage)
currentCommitMessage = *&CommitMessage{"", "", "", "", nil}
currentFileChanges = nil
}
}
}
package gitt
import "time"
type CodeAge struct {
File string
Age time.Time
}
type CodeAgeDisplay struct {
File string
Month string
}
type TeamSummary struct {
EntityName string
AuthorCount int
RevsCount int
}
type TeamInformation struct {
EntityName string
Authors map[string]string
Revs map[string]string
}
type GitSummary struct {
Commits int
Entities int
Changes int
Authors int
}
type CommitMessage struct {
Rev string
Author string
Date string
Message string
Changes []FileChange
}
type FileChange struct {
Added int
Deleted int
File string
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册