Skip to content

Implement contributors graph #27880

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 103 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
103 commits
Select commit Hold shift + click to select a range
9fd24bc
Create new page
sahinakkaya Jun 16, 2023
3975abd
Implement simple api
sahinakkaya Jun 18, 2023
19deef6
Implement basic graph render
sahinakkaya Jun 19, 2023
715fc15
Handle dates by grouping them into weeks
sahinakkaya Jun 19, 2023
136d8be
Split vue file into meaningful pieces
sahinakkaya Jun 19, 2023
be50ead
Remove non-needed code
sahinakkaya Jun 19, 2023
3211417
Implement contributors graph
sahinakkaya Jun 20, 2023
88247fd
Switch to unix timestamp
sahinakkaya Jun 20, 2023
a393093
Show contributor rank
sahinakkaya Jun 20, 2023
e8e0e34
Multiply x with 1000 to make it milliseconds
sahinakkaya Jun 20, 2023
ff17e01
Remove legend, format numbers
sahinakkaya Jun 20, 2023
664747a
Implement contribution type selection
sahinakkaya Jun 20, 2023
2c567ea
Draw charts basaed on selected contribution type
sahinakkaya Jun 20, 2023
8ef0c35
Show all sundays until today instead of latest commit
sahinakkaya Jun 20, 2023
bc396c0
Show date range in title
sahinakkaya Jun 20, 2023
630c2dd
Add zoom/pan support to charts
sahinakkaya Jun 21, 2023
d293f24
Sync charts on zoom/pan events
sahinakkaya Jun 21, 2023
2304a5d
Format everything with gofmt
sahinakkaya Jun 21, 2023
a047e07
Disable animations for performance
sahinakkaya Jun 21, 2023
34f9d92
Reset zoom when clicking on chart
sahinakkaya Jun 21, 2023
f210a51
Disable grid for x axis
sahinakkaya Jun 22, 2023
9af88ec
Refactor
sahinakkaya Jun 22, 2023
4399890
Remove unused api endpoint
sahinakkaya Jun 22, 2023
e94b9c7
Merge branch 'main' into main
sahinakkaya Jun 22, 2023
a4b6666
Use a better name for variable
sahinakkaya Jun 22, 2023
c75b7a5
Implement new endpoint for fetching contributors data
sahinakkaya Jun 22, 2023
e29efb2
Use new endpoint for fetching contributors data
sahinakkaya Jun 22, 2023
b516a67
Introduce a new endpoint for fetching data in frontend
sahinakkaya Jun 22, 2023
4ee972c
Apply suggestions from code review
sahinakkaya Jun 23, 2023
90e3d50
Do not use hard-coded colors
sahinakkaya Jun 23, 2023
0dfd908
Make object key snake_case
sahinakkaya Jun 24, 2023
2c174d6
Add copyright header
sahinakkaya Jun 24, 2023
730368e
Fix linting errors
sahinakkaya Jun 24, 2023
32fe51e
Use actual commit ID instead of empty string
sahinakkaya Jun 24, 2023
5e16adf
Merge branch 'main' into main
6543 Jun 24, 2023
0dd9095
Generate swagger
sahinakkaya Jun 24, 2023
ffab20b
Merge branch 'main' into main
sahinakkaya Jun 24, 2023
3196641
Lower height of graph
sahinakkaya Jun 24, 2023
5e66e27
Merge branch 'main' into main
sahinakkaya Jun 25, 2023
a886f57
Remove translation to other languages
sahinakkaya Jun 26, 2023
f278598
Add newline at end of swagger file
sahinakkaya Jun 26, 2023
beb7382
Move header from Go template to Vue
sahinakkaya Jun 26, 2023
e169731
Merge branch 'main' into main
sahinakkaya Jun 26, 2023
4c5170b
Merge branch 'main' into main
sahinakkaya Jun 28, 2023
8dce947
Fix package-lock.json
sahinakkaya Jun 28, 2023
4f74932
Adjust the location of the contributors page
hiifong Jun 28, 2023
677ac20
Merge branch 'go-gitea:main' into contributors
hiifong Jun 28, 2023
1cbeabf
fix lint
hiifong Jun 28, 2023
e047458
make generate-swagger
hiifong Jun 28, 2023
49eaf6d
fix typo
hiifong Jun 28, 2023
3dcbba3
Merge pull request #1 from hiifong/contributors
sahinakkaya Jun 28, 2023
a8d2a9c
Merge branch 'main' into main
sahinakkaya Jun 30, 2023
3e156ab
Cherry-pick 64f2d7026 and resolve conflicts
silverwind Jun 29, 2023
7947bf9
Try updating date range when zoom or pan occurs
sahinakkaya Jun 30, 2023
3d62edc
Use v-memo to avoid unnecessary re-render
sahinakkaya Jun 30, 2023
e27ea6c
Merge branch 'update-date'
sahinakkaya Jun 30, 2023
e5537cc
Preparation step for sorting contributor graphs
sahinakkaya Jun 30, 2023
7a69f2e
Update contributor graphs based on selected date range
sahinakkaya Jun 30, 2023
5d2b764
Calculate total stats while looping to avoid unnecessary computation …
sahinakkaya Jun 30, 2023
d0714e7
Sort contributors based on selected contribution type
sahinakkaya Jun 30, 2023
b273ba7
Do not show contributor graph if total contribution is 0 on selected …
sahinakkaya Jun 30, 2023
1e163bb
Set highest value for contributor graphs dynamically
sahinakkaya Jun 30, 2023
e4ccffd
Fix a bug while parsing the commits
sahinakkaya Jun 30, 2023
1ba2157
Merge branch 'main' into main
sahinakkaya Jun 30, 2023
6ffe1f1
Fix linting errors
sahinakkaya Jun 30, 2023
43bf7ec
Merge branch 'main' into main
sahinakkaya Jul 1, 2023
497ce76
Fix linting errors
sahinakkaya Jul 1, 2023
145d3ca
Fix linting errors
sahinakkaya Jul 1, 2023
d550ba7
Merge branch 'main' into main
sahinakkaya Jul 2, 2023
991e3c0
Show loading animation while fetching data
sahinakkaya Jul 6, 2023
8ba3674
Eliminate unnecessary fetch when contribution type changes
sahinakkaya Jul 6, 2023
b148d1a
Reset all charts when main chart is clicked
sahinakkaya Jul 6, 2023
5de9fbf
Handle errors which can occure while fetching data
sahinakkaya Jul 6, 2023
79dfb84
Apply suggestions from code review
sahinakkaya Jul 7, 2023
ec30e3b
Apply suggestions from code review
sahinakkaya Jul 7, 2023
51f8118
Use translated strings
sahinakkaya Jul 9, 2023
7e68c7c
Do code splitting on contributors page
sahinakkaya Jul 9, 2023
056745b
Use author information instead of committer
sahinakkaya Jul 9, 2023
d6887e4
Move contributor related code under service
sahinakkaya Jul 9, 2023
8ed40fe
Apply suggestions from code review
sahinakkaya Jul 14, 2023
3fc79af
Apply suggestions from code review
sahinakkaya Jul 14, 2023
d51417d
Merge branch 'main' into main
6543 Jul 14, 2023
75b92e3
Merge remote-tracking branch 'upstream/main' into main
sahinakkaya Oct 17, 2023
09d249d
Apply suggestions from code review
sahinakkaya Oct 17, 2023
023bbbb
Use GET instead of fetch
sahinakkaya Oct 17, 2023
e83b562
Re-order tags to script - template - style in vue file
sahinakkaya Oct 17, 2023
f2d3394
Remove useless error checking
sahinakkaya Oct 18, 2023
ca59188
Merge branch 'main' into main
6543 Oct 20, 2023
7f1f49f
Merge remote-tracking branch 'upstream/main'
sahinakkaya Oct 26, 2023
e953057
Merge remote-tracking branch 'upstream/main'
sahinakkaya Oct 30, 2023
88ffba5
Apply suggestions from code review
sahinakkaya Oct 31, 2023
12cc6d6
Merge remote-tracking branch 'upstream/main'
sahinakkaya Oct 31, 2023
ce1306b
Merge branch 'main' into main
6543 Nov 1, 2023
80e6614
Fix linting errors
sahinakkaya Nov 1, 2023
8d07fdd
Merge branch 'main' into main
6543 Nov 1, 2023
248065f
dayjs
6543 Nov 1, 2023
ba4e738
generate
6543 Nov 1, 2023
b6b4881
fmt
6543 Nov 2, 2023
9e50232
Merge branch 'main' into sahinakkaya/main
6543 Nov 2, 2023
2bf4ce0
Fix locale usage
sahinakkaya Nov 2, 2023
5b52a1f
Merge branch 'main' into main
silverwind Nov 2, 2023
064c5ad
Merge branch 'main' into main
silverwind Nov 2, 2023
b71882c
layout fixes
silverwind Nov 2, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
97 changes: 97 additions & 0 deletions modules/git/repo_commit.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,20 @@
package git

import (
"bufio"
"bytes"
"context"
"encoding/hex"
"fmt"
"io"
"os"
"strconv"
"strings"

"code.gitea.io/gitea/modules/cache"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
api "code.gitea.io/gitea/modules/structs"
)

// GetBranchCommitID returns last commit ID string of given branch.
Expand Down Expand Up @@ -212,6 +217,98 @@ type CommitsByFileAndRangeOptions struct {
Page int
}

// ExtendedCommitStats return the list of *api.ExtendedCommitStats for the given revision
func (repo *Repository) ExtendedCommitStats(revision string /*, limit int */) ([]*api.ExtendedCommitStats, error) {
baseCommit, err := repo.GetCommit(revision)
if err != nil {
return nil, err
}
stdoutReader, stdoutWriter, err := os.Pipe()
if err != nil {
return nil, err
}
defer func() {
_ = stdoutReader.Close()
_ = stdoutWriter.Close()
}()

gitCmd := NewCommand(repo.Ctx, "log", "--shortstat", "--no-merges", "--pretty=format:---%n%aN%n%aE%n%as", "--reverse")
// AddOptionFormat("--max-count=%d", limit)
gitCmd.AddDynamicArguments(baseCommit.ID.String())

var extendedCommitStats []*api.ExtendedCommitStats
stderr := new(strings.Builder)
err = gitCmd.Run(&RunOpts{
Dir: repo.Path,
Stdout: stdoutWriter,
Stderr: stderr,
PipelineFunc: func(ctx context.Context, cancel context.CancelFunc) error {
_ = stdoutWriter.Close()
scanner := bufio.NewScanner(stdoutReader)
scanner.Split(bufio.ScanLines)

for scanner.Scan() {
line := strings.TrimSpace(scanner.Text())
if line != "---" {
continue
}
scanner.Scan()
authorName := strings.TrimSpace(scanner.Text())
scanner.Scan()
authorEmail := strings.TrimSpace(scanner.Text())
scanner.Scan()
date := strings.TrimSpace(scanner.Text())
scanner.Scan()
stats := strings.TrimSpace(scanner.Text())
if authorName == "" || authorEmail == "" || date == "" || stats == "" {
// FIXME: find a better way to parse the output so that we will handle this properly
log.Warn("Something is wrong with git log output, skipping...")
log.Warn("authorName: %s, authorEmail: %s, date: %s, stats: %s", authorName, authorEmail, date, stats)
continue
}
// 1 file changed, 1 insertion(+), 1 deletion(-)
fields := strings.Split(stats, ",")

commitStats := api.CommitStats{}
for _, field := range fields[1:] {
parts := strings.Split(strings.TrimSpace(field), " ")
value, contributionType := parts[0], parts[1]
amount, _ := strconv.Atoi(value)

if strings.HasPrefix(contributionType, "insertion") {
commitStats.Additions = amount
} else {
commitStats.Deletions = amount
}
}
commitStats.Total = commitStats.Additions + commitStats.Deletions
scanner.Scan()
scanner.Text() // empty line at the end

res := &api.ExtendedCommitStats{
Author: &api.CommitUser{
Identity: api.Identity{
Name: authorName,
Email: authorEmail,
},
Date: date,
},
Stats: &commitStats,
}
extendedCommitStats = append(extendedCommitStats, res)

}
_ = stdoutReader.Close()
return nil
},
})
if err != nil {
return nil, fmt.Errorf("Failed to get ContributorsCommitStats for repository.\nError: %w\nStderr: %s", err, stderr)
}

return extendedCommitStats, nil
}

// CommitsByFileAndRange return the commits according revision file and the page
func (repo *Repository) CommitsByFileAndRange(opts CommitsByFileAndRangeOptions) ([]*Commit, error) {
skip := (opts.Page - 1) * setting.Git.CommitsRangeSize
Expand Down
17 changes: 17 additions & 0 deletions modules/structs/repo_collaborator.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,20 @@ type RepoCollaboratorPermission struct {
RoleName string `json:"role_name"`
User *User `json:"user"`
}

type WeekData struct {
Week int64 `json:"week"` // Starting day of the week as Unix timestamp
Additions int `json:"additions"` // Number of additions in that week
Deletions int `json:"deletions"` // Number of deletions in that week
Commits int `json:"commits"` // Number of commits in that week
}

// ContributorData represents statistical git commit count data
type ContributorData struct {
Name string `json:"name"` // Display name of the contributor
Login string `json:"login"` // Login name of the contributor in case it exists
AvatarLink string `json:"avatar_link"`
HomeLink string `json:"home_link"`
TotalCommits int64 `json:"total_commits"`
Weeks []*WeekData `json:"weeks"`
}
6 changes: 6 additions & 0 deletions modules/structs/repo_commit.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,12 @@ type Commit struct {
Stats *CommitStats `json:"stats"`
}

// ExtendedCommitStats contains information for commit stats with author data
type ExtendedCommitStats struct {
Author *CommitUser `json:"author"`
Stats *CommitStats `json:"stats"`
}

// CommitDateOptions store dates for GIT_AUTHOR_DATE and GIT_COMMITTER_DATE
type CommitDateOptions struct {
// swagger:strfmt date-time
Expand Down
68 changes: 68 additions & 0 deletions modules/util/dates.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT

package util

import (
"time"
)

const layout = "2006-01-02"

func ListSundaysBetween(startStr, endStr string) ([]int64, error) {
startDate, err := time.Parse(layout, startStr)
if err != nil {
return nil, err
}

endDate, err := time.Parse(layout, endStr)
if err != nil {
return nil, err
}

// Ensure the start date is a Sunday
for startDate.Weekday() != time.Sunday {
startDate = startDate.AddDate(0, 0, 1)
}

var sundays []int64

// Iterate from start date to end date and find all Sundays
for currentDate := startDate; currentDate.Before(endDate); currentDate = currentDate.AddDate(0, 0, 7) {
sundays = append(sundays, currentDate.UnixMilli())
}

return sundays, nil
}

func FindLastSundayBeforeDate(dateStr string) (string, error) {
date, err := time.Parse(layout, dateStr)
if err != nil {
return "", err
}

weekday := date.Weekday()
daysToSubtract := int(weekday) - int(time.Sunday)
if daysToSubtract < 0 {
daysToSubtract += 7
}

lastSunday := date.AddDate(0, 0, -daysToSubtract)
return lastSunday.Format(layout), nil
}

func FindFirstSundayAfterDate(dateStr string) (string, error) {
date, err := time.Parse(layout, dateStr)
if err != nil {
return "", err
}

weekday := date.Weekday()
daysToAdd := int(time.Sunday) - int(weekday)
if daysToAdd <= 0 {
daysToAdd += 7
}

firstSunday := date.AddDate(0, 0, daysToAdd)
return firstSunday.Format(layout), nil
}
12 changes: 12 additions & 0 deletions options/locale/locale_en-US.ini
Original file line number Diff line number Diff line change
Expand Up @@ -1898,6 +1898,8 @@ wiki.page_name_desc = Enter a name for this Wiki page. Some special names are: '
wiki.original_git_entry_tooltip = View original Git file instead of using friendly link.

activity = Activity
activity.navbar.pulse = Pulse
activity.navbar.contributors = Contributors
activity.period.filter_label = Period:
activity.period.daily = 1 day
activity.period.halfweekly = 3 days
Expand Down Expand Up @@ -1963,6 +1965,16 @@ activity.git_stats_and_deletions = and
activity.git_stats_deletion_1 = %d deletion
activity.git_stats_deletion_n = %d deletions

contributors = Contributors
contributors.contribution_type.filter_label = Contribution type:
contributors.contribution_type.commits = Commits
contributors.contribution_type.additions = Additions
contributors.contribution_type.deletions = Deletions
contributors.loading_title = Loading contributions...
contributors.loading_title_failed = Could not load contributions
contributors.loading_info = This might take a bit…
contributors.component_failed_to_load = An unexpected error happened.

search = Search
search.search_repo = Search repository
search.type.tooltip = Search type
Expand Down
61 changes: 61 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,12 @@
"add-asset-webpack-plugin": "2.0.1",
"ansi_up": "6.0.2",
"asciinema-player": "3.6.2",
"chart.js": "4.3.0",
"chartjs-adapter-dayjs-4": "1.0.4",
"chartjs-plugin-zoom": "2.0.1",
"clippie": "4.0.6",
"css-loader": "6.8.1",
"dayjs": "1.11.10",
"dropzone": "6.0.0-beta.2",
"easymde": "2.18.0",
"esbuild-loader": "4.0.2",
Expand All @@ -46,6 +50,7 @@
"uint8-to-base64": "0.2.0",
"vue": "3.3.4",
"vue-bar-graph": "2.0.0",
"vue-chartjs": "5.2.0",
"vue-loader": "17.3.0",
"vue3-calendar-heatmap": "2.0.5",
"webpack": "5.88.2",
Expand Down
1 change: 1 addition & 0 deletions routers/api/v1/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -1234,6 +1234,7 @@ func Routes() *web.Route {
m.Get("/statuses", repo.GetCommitStatusesByRef)
}, context.ReferencesGitRepo())
}, reqRepoReader(unit.TypeCode))
m.Get("/contributors", context.ReferencesGitRepo(), reqRepoReader(unit.TypeCode), repo.GetAllContributorsStats)
m.Group("/git", func() {
m.Group("/commits", func() {
m.Get("/{sha}", repo.GetSingleCommit)
Expand Down
Loading