Merge branch 'master'
This commit is contained in:
@@ -124,7 +124,6 @@ func getFileHashes(c *object.Commit, treePath string, paths []string) (map[strin
|
||||
|
||||
func getLastCommitForPaths(c *object.Commit, treePath string, paths []string) (map[string]*object.Commit, error) {
|
||||
// We do a tree traversal with nodes sorted by commit time
|
||||
seen := make(map[plumbing.Hash]bool)
|
||||
heap := binaryheap.NewWith(func(a, b interface{}) int {
|
||||
if a.(*commitAndPaths).commit.Committer.When.Before(b.(*commitAndPaths).commit.Committer.When) {
|
||||
return 1
|
||||
@@ -202,15 +201,10 @@ func getLastCommitForPaths(c *object.Commit, treePath string, paths []string) (m
|
||||
// Add the parent nodes along with remaining paths to the heap for further
|
||||
// processing.
|
||||
for j, parent := range parents {
|
||||
if seen[parent.ID()] {
|
||||
continue
|
||||
}
|
||||
seen[parent.ID()] = true
|
||||
|
||||
// Combine remainingPath with paths available on the parent branch
|
||||
// and make union of them
|
||||
var remainingPathsForParent []string
|
||||
var newRemainingPaths []string
|
||||
remainingPathsForParent := make([]string, 0, len(remainingPaths))
|
||||
newRemainingPaths := make([]string, 0, len(remainingPaths))
|
||||
for _, path := range remainingPaths {
|
||||
if parentHashes[j][path] == current.hashes[path] {
|
||||
remainingPathsForParent = append(remainingPathsForParent, path)
|
||||
|
||||
@@ -9,9 +9,11 @@ import (
|
||||
"bytes"
|
||||
"container/list"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -306,3 +308,40 @@ func GetLatestCommitTime(repoPath string) (time.Time, error) {
|
||||
commitTime := strings.TrimSpace(stdout)
|
||||
return time.Parse(GitTimeLayout, commitTime)
|
||||
}
|
||||
|
||||
// DivergeObject represents commit count diverging commits
|
||||
type DivergeObject struct {
|
||||
Ahead int
|
||||
Behind int
|
||||
}
|
||||
|
||||
func checkDivergence(repoPath string, baseBranch string, targetBranch string) (int, error) {
|
||||
branches := fmt.Sprintf("%s..%s", baseBranch, targetBranch)
|
||||
cmd := NewCommand("rev-list", "--count", branches)
|
||||
stdout, err := cmd.RunInDir(repoPath)
|
||||
if err != nil {
|
||||
return -1, err
|
||||
}
|
||||
outInteger, errInteger := strconv.Atoi(strings.Trim(stdout, "\n"))
|
||||
if errInteger != nil {
|
||||
return -1, errInteger
|
||||
}
|
||||
return outInteger, nil
|
||||
}
|
||||
|
||||
// GetDivergingCommits returns the number of commits a targetBranch is ahead or behind a baseBranch
|
||||
func GetDivergingCommits(repoPath string, baseBranch string, targetBranch string) (DivergeObject, error) {
|
||||
// $(git rev-list --count master..feature) commits ahead of master
|
||||
ahead, errorAhead := checkDivergence(repoPath, baseBranch, targetBranch)
|
||||
if errorAhead != nil {
|
||||
return DivergeObject{}, errorAhead
|
||||
}
|
||||
|
||||
// $(git rev-list --count feature..master) commits behind master
|
||||
behind, errorBehind := checkDivergence(repoPath, targetBranch, baseBranch)
|
||||
if errorBehind != nil {
|
||||
return DivergeObject{}, errorBehind
|
||||
}
|
||||
|
||||
return DivergeObject{ahead, behind}, nil
|
||||
}
|
||||
|
||||
108
modules/git/repo_stats.go
Normal file
108
modules/git/repo_stats.go
Normal file
@@ -0,0 +1,108 @@
|
||||
// Copyright 2019 The Gitea Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package git
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// CodeActivityStats represents git statistics data
|
||||
type CodeActivityStats struct {
|
||||
AuthorCount int64
|
||||
CommitCount int64
|
||||
ChangedFiles int64
|
||||
Additions int64
|
||||
Deletions int64
|
||||
CommitCountInAllBranches int64
|
||||
Authors map[string]int64
|
||||
}
|
||||
|
||||
// GetCodeActivityStats returns code statistics for acitivity page
|
||||
func (repo *Repository) GetCodeActivityStats(fromTime time.Time, branch string) (*CodeActivityStats, error) {
|
||||
stats := &CodeActivityStats{}
|
||||
|
||||
since := fromTime.Format(time.RFC3339)
|
||||
|
||||
stdout, err := NewCommand("rev-list", "--count", "--no-merges", "--branches=*", "--date=iso", fmt.Sprintf("--since='%s'", since)).RunInDirBytes(repo.Path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
c, err := strconv.ParseInt(strings.TrimSpace(string(stdout)), 10, 64)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
stats.CommitCountInAllBranches = c
|
||||
|
||||
args := []string{"log", "--numstat", "--no-merges", "--pretty=format:---%n%h%n%an%n%ae%n", "--date=iso", fmt.Sprintf("--since='%s'", since)}
|
||||
if len(branch) == 0 {
|
||||
args = append(args, "--branches=*")
|
||||
} else {
|
||||
args = append(args, "--first-parent", branch)
|
||||
}
|
||||
|
||||
stdout, err = NewCommand(args...).RunInDirBytes(repo.Path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
scanner := bufio.NewScanner(bytes.NewReader(stdout))
|
||||
scanner.Split(bufio.ScanLines)
|
||||
stats.CommitCount = 0
|
||||
stats.Additions = 0
|
||||
stats.Deletions = 0
|
||||
authors := make(map[string]int64)
|
||||
files := make(map[string]bool)
|
||||
p := 0
|
||||
for scanner.Scan() {
|
||||
l := strings.TrimSpace(scanner.Text())
|
||||
if l == "---" {
|
||||
p = 1
|
||||
} else if p == 0 {
|
||||
continue
|
||||
} else {
|
||||
p++
|
||||
}
|
||||
if p > 4 && len(l) == 0 {
|
||||
continue
|
||||
}
|
||||
switch p {
|
||||
case 1: // Separator
|
||||
case 2: // Commit sha-1
|
||||
stats.CommitCount++
|
||||
case 3: // Author
|
||||
case 4: // E-mail
|
||||
email := strings.ToLower(l)
|
||||
i := authors[email]
|
||||
authors[email] = i + 1
|
||||
default: // Changed file
|
||||
if parts := strings.Fields(l); len(parts) >= 3 {
|
||||
if parts[0] != "-" {
|
||||
if c, err := strconv.ParseInt(strings.TrimSpace(parts[0]), 10, 64); err == nil {
|
||||
stats.Additions += c
|
||||
}
|
||||
}
|
||||
if parts[1] != "-" {
|
||||
if c, err := strconv.ParseInt(strings.TrimSpace(parts[1]), 10, 64); err == nil {
|
||||
stats.Deletions += c
|
||||
}
|
||||
}
|
||||
if _, ok := files[parts[2]]; !ok {
|
||||
files[parts[2]] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
stats.AuthorCount = int64(len(authors))
|
||||
stats.ChangedFiles = int64(len(files))
|
||||
stats.Authors = authors
|
||||
|
||||
return stats, nil
|
||||
}
|
||||
35
modules/git/repo_stats_test.go
Normal file
35
modules/git/repo_stats_test.go
Normal file
@@ -0,0 +1,35 @@
|
||||
// Copyright 2019 The Gitea Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package git
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestRepository_GetCodeActivityStats(t *testing.T) {
|
||||
bareRepo1Path := filepath.Join(testReposDir, "repo1_bare")
|
||||
bareRepo1, err := OpenRepository(bareRepo1Path)
|
||||
assert.NoError(t, err)
|
||||
|
||||
timeFrom, err := time.Parse(time.RFC3339, "2016-01-01T00:00:00+00:00")
|
||||
|
||||
code, err := bareRepo1.GetCodeActivityStats(timeFrom, "")
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, code)
|
||||
|
||||
assert.EqualValues(t, 8, code.CommitCount)
|
||||
assert.EqualValues(t, 2, code.AuthorCount)
|
||||
assert.EqualValues(t, 8, code.CommitCountInAllBranches)
|
||||
assert.EqualValues(t, 10, code.Additions)
|
||||
assert.EqualValues(t, 1, code.Deletions)
|
||||
assert.Len(t, code.Authors, 2)
|
||||
assert.Contains(t, code.Authors, "tris.git@shoddynet.org")
|
||||
assert.EqualValues(t, 3, code.Authors["tris.git@shoddynet.org"])
|
||||
assert.EqualValues(t, 5, code.Authors[""])
|
||||
}
|
||||
@@ -35,14 +35,15 @@ func (repo *Repository) GetTree(idStr string) (*Tree, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resolvedID := id
|
||||
commitObject, err := repo.gogitRepo.CommitObject(plumbing.Hash(id))
|
||||
if err == nil {
|
||||
id = SHA1(commitObject.TreeHash)
|
||||
}
|
||||
treeObject, err := repo.getTree(id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
treeObject, err := repo.getTree(SHA1(commitObject.TreeHash))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
treeObject.CommitID = id
|
||||
treeObject.ResolvedID = resolvedID
|
||||
return treeObject, nil
|
||||
}
|
||||
|
||||
@@ -15,9 +15,9 @@ import (
|
||||
|
||||
// Tree represents a flat directory listing.
|
||||
type Tree struct {
|
||||
ID SHA1
|
||||
CommitID SHA1
|
||||
repo *Repository
|
||||
ID SHA1
|
||||
ResolvedID SHA1
|
||||
repo *Repository
|
||||
|
||||
gogitTree *object.Tree
|
||||
|
||||
@@ -106,7 +106,7 @@ func (t *Tree) ListEntriesRecursive() (Entries, error) {
|
||||
seen := map[plumbing.Hash]bool{}
|
||||
walker := object.NewTreeWalker(t.gogitTree, true, seen)
|
||||
for {
|
||||
_, entry, err := walker.Next()
|
||||
fullName, entry, err := walker.Next()
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
@@ -121,6 +121,7 @@ func (t *Tree) ListEntriesRecursive() (Entries, error) {
|
||||
ID: entry.Hash,
|
||||
gogitTreeEntry: &entry,
|
||||
ptree: t,
|
||||
fullName: fullName,
|
||||
}
|
||||
entries = append(entries, convertedEntry)
|
||||
}
|
||||
|
||||
@@ -40,12 +40,16 @@ type TreeEntry struct {
|
||||
gogitTreeEntry *object.TreeEntry
|
||||
ptree *Tree
|
||||
|
||||
size int64
|
||||
sized bool
|
||||
size int64
|
||||
sized bool
|
||||
fullName string
|
||||
}
|
||||
|
||||
// Name returns the name of the entry
|
||||
func (te *TreeEntry) Name() string {
|
||||
if te.fullName != "" {
|
||||
return te.fullName
|
||||
}
|
||||
return te.gogitTreeEntry.Name
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user