Skip to content

Commit 7914577

Browse files
authored
Merge pull request #13 from thetnaingtn/improvement/get-repos-list
Improvement on get repo detail
2 parents 15a329e + 0359c33 commit 7914577

File tree

3 files changed

+128
-75
lines changed

3 files changed

+128
-75
lines changed

forky.go

+108-63
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"log"
77
"net/http"
88
"sort"
9+
"sync"
910

1011
"github.com/google/go-github/v52/github"
1112
)
@@ -26,65 +27,31 @@ type RepositoryWithDetails struct {
2627
ParentDeleted bool
2728
Private bool
2829
BehindBy int
30+
Error error
2931
}
3032

3133
func GetForks(ctx context.Context, client *github.Client) ([]*RepositoryWithDetails, error) {
32-
var forks []*RepositoryWithDetails
33-
repos, err := getAllRepositories(context.Background(), client)
34+
var forksWithDetails []*RepositoryWithDetails
35+
forks, err := getAllForks(context.Background(), client)
3436

3537
if err != nil {
36-
return forks, fmt.Errorf("failed to fetch fork list: %w\n", err)
38+
return forksWithDetails, fmt.Errorf("failed to fetch fork list: %w\n", err)
3739
}
3840

39-
for _, r := range repos {
40-
if !r.GetFork() {
41-
continue
42-
}
43-
repo, resp, err := client.Repositories.Get(ctx, r.GetOwner().GetLogin(), r.GetName())
44-
45-
switch resp.StatusCode {
46-
case http.StatusForbidden:
47-
continue
48-
case http.StatusUnavailableForLegalReasons:
49-
forks = append(forks, buildDetails(r, nil, resp.StatusCode))
50-
continue
51-
}
52-
53-
if err != nil {
54-
return forks, fmt.Errorf("failed to get repository %s: %w", r.GetName(), err)
55-
}
56-
57-
parent := repo.GetParent()
58-
59-
base := fmt.Sprintf("%s:%s", parent.GetOwner().GetLogin(), repo.GetDefaultBranch()) // compare with forked repo's default branch
60-
head := fmt.Sprintf("%s:%s", repo.GetOwner().GetLogin(), repo.GetDefaultBranch())
61-
cmpr, resp, err := client.Repositories.CompareCommits(
62-
ctx,
63-
repo.GetOwner().GetLogin(),
64-
repo.GetName(),
65-
base,
66-
head,
67-
&github.ListOptions{},
68-
)
69-
70-
if err != nil && resp.StatusCode != http.StatusNotFound {
71-
log.Println("ERR", err)
72-
return forks, fmt.Errorf("failed to compare repository with parent %s: %w", parent.GetName(), err)
73-
}
41+
forkStream := getReposDetail(ctx, client, forks)
7442

75-
if cmpr.GetBehindBy() < 1 {
43+
for fork := range forkStream {
44+
if fork.Error == nil && fork.BehindBy < 1 {
7645
continue
7746
}
78-
79-
forks = append(forks, buildDetails(repo, cmpr, resp.StatusCode))
80-
47+
forksWithDetails = append(forksWithDetails, fork)
8148
}
8249

83-
sort.SliceStable(forks, func(i, j int) bool {
84-
return forks[i].BehindBy > forks[j].BehindBy
50+
sort.SliceStable(forksWithDetails, func(i, j int) bool {
51+
return forksWithDetails[i].BehindBy > forksWithDetails[j].BehindBy
8552
})
8653

87-
return forks, nil
54+
return forksWithDetails, nil
8855
}
8956

9057
func SyncBranchWithUpstreamRepo(client *github.Client, repo *RepositoryWithDetails) error {
@@ -102,40 +69,109 @@ func SyncBranchWithUpstreamRepo(client *github.Client, repo *RepositoryWithDetai
10269
return nil
10370
}
10471

72+
func getReposDetail(ctx context.Context, client *github.Client, forks []*github.Repository) <-chan *RepositoryWithDetails {
73+
var wg sync.WaitGroup
74+
done := make(chan interface{})
75+
forkStream := make(chan *RepositoryWithDetails, len(forks))
76+
77+
defer close(done)
78+
defer close(forkStream)
79+
80+
wg.Add(len(forks))
81+
82+
for _, fork := range forks {
83+
go func(fork *github.Repository) {
84+
defer wg.Done()
85+
select {
86+
case <-done:
87+
return
88+
default:
89+
repo, resp, err := client.Repositories.Get(ctx, fork.GetOwner().GetLogin(), fork.GetName())
90+
if err != nil {
91+
log.Println("getReposDetail", err)
92+
forkStream <- &RepositoryWithDetails{Error: fmt.Errorf("failed to get repository %s: %w", fork.GetName(), err)}
93+
return
94+
}
95+
96+
parent := repo.GetParent()
97+
98+
base := fmt.Sprintf("%s:%s", parent.GetOwner().GetLogin(), repo.GetDefaultBranch()) // compare with forked repo's default branch
99+
head := fmt.Sprintf("%s:%s", repo.GetOwner().GetLogin(), repo.GetDefaultBranch())
100+
101+
cmpr, resp, err := client.Repositories.CompareCommits(
102+
ctx,
103+
repo.GetOwner().GetLogin(),
104+
repo.GetName(),
105+
base,
106+
head,
107+
&github.ListOptions{},
108+
)
109+
110+
if err != nil && resp.StatusCode == http.StatusNotFound {
111+
log.Println("getReposDetail", err)
112+
repoWithDetail := buildDetails(repo, nil, resp.StatusCode)
113+
repoWithDetail.Error = fmt.Errorf("can't find %s branch on %s", head, parent.GetFullName())
114+
forkStream <- repoWithDetail
115+
return
116+
}
117+
118+
if err != nil && resp.StatusCode != http.StatusNotFound {
119+
log.Println("getReposDetail", err)
120+
repoWithDetail := buildDetails(repo, nil, resp.StatusCode)
121+
repoWithDetail.Error = fmt.Errorf("failed to compare repository with parent %s: %w", parent.GetName(), err)
122+
forkStream <- repoWithDetail
123+
return
124+
}
125+
126+
forkStream <- buildDetails(repo, cmpr, resp.StatusCode)
127+
}
128+
129+
}(fork)
130+
}
131+
132+
wg.Wait()
133+
134+
return forkStream
135+
}
136+
105137
func buildDetails(repo *github.Repository, commit *github.CommitsComparison, code int) *RepositoryWithDetails {
106-
if repo == nil || commit == nil {
107-
return &RepositoryWithDetails{}
138+
repoWithDetails := &RepositoryWithDetails{
139+
ParentDeleted: code == http.StatusNotFound,
140+
}
141+
142+
if commit != nil {
143+
repoWithDetails.BehindBy = commit.GetBehindBy()
108144
}
109145

110-
return &RepositoryWithDetails{
111-
Owner: repo.GetOwner().GetLogin(),
112-
Name: repo.GetName(),
113-
FullName: repo.GetFullName(),
114-
Description: repo.GetDescription(),
115-
RepoURL: repo.GetURL(),
116-
DefaultBranch: repo.GetDefaultBranch(),
117-
Parent: repo.GetParent().GetOwner().GetLogin(),
118-
ParentFullName: repo.GetParent().GetFullName(),
119-
ParentDeleted: code == http.StatusNotFound,
120-
Private: repo.GetPrivate(),
121-
BehindBy: commit.GetBehindBy(),
146+
if repo != nil {
147+
repoWithDetails.Owner = repo.GetOwner().GetLogin()
148+
repoWithDetails.Name = repo.GetName()
149+
repoWithDetails.FullName = repo.GetFullName()
150+
repoWithDetails.Description = repo.GetDescription()
151+
repoWithDetails.RepoURL = repo.GetURL()
152+
repoWithDetails.DefaultBranch = repo.GetDefaultBranch()
153+
repoWithDetails.Parent = repo.GetParent().GetOwner().GetLogin()
154+
repoWithDetails.ParentFullName = repo.GetParent().GetFullName()
155+
repoWithDetails.Private = repo.GetPrivate()
122156
}
157+
158+
return repoWithDetails
123159
}
124160

125-
func getAllRepositories(ctx context.Context, client *github.Client) ([]*github.Repository, error) {
161+
func getAllForks(ctx context.Context, client *github.Client) ([]*github.Repository, error) {
126162
var allRepos []*github.Repository
127163
opts := &github.RepositoryListOptions{
128164
Type: "owner",
129165
ListOptions: github.ListOptions{PerPage: pageSize},
130166
}
131167

132168
for {
133-
repos, resp, err := client.Repositories.List(ctx, "", opts)
169+
forks, resp, err := client.Repositories.List(ctx, "", opts)
134170
if err != nil {
135171
return allRepos, err
136172
}
137173

138-
allRepos = append(allRepos, repos...)
174+
allRepos = append(allRepos, forks...)
139175

140176
if resp.NextPage == 0 {
141177
break
@@ -144,5 +180,14 @@ func getAllRepositories(ctx context.Context, client *github.Client) ([]*github.R
144180
opts.Page = resp.NextPage
145181
}
146182

147-
return allRepos, nil
183+
forks := make([]*github.Repository, 0, len(allRepos))
184+
for _, repo := range allRepos {
185+
if !repo.GetFork() {
186+
continue
187+
}
188+
189+
forks = append(forks, repo)
190+
}
191+
192+
return forks, nil
148193
}

ui/app.go

+6
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@ type AppModel struct {
1919
func (m AppModel) toggleSelection() tea.Cmd {
2020
idx := m.list.Index()
2121
item := m.list.SelectedItem().(item)
22+
if item.repo.Error != nil {
23+
return nil
24+
}
2225
item.selected = !item.selected
2326
m.list.RemoveItem(idx)
2427

@@ -30,6 +33,9 @@ func (m AppModel) changeSelect(selected bool) []tea.Cmd {
3033

3134
for idx, i := range m.list.Items() {
3235
item := i.(item)
36+
if item.repo.Error != nil {
37+
continue
38+
}
3339
item.selected = selected
3440
m.list.RemoveItem(idx)
3541
cmds = append(cmds, m.list.InsertItem(idx, item))

ui/item.go

+14-12
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,8 @@ func (i item) Title() string {
2828
return iconSynced + " " + titleStr
2929
}
3030

31-
if !i.synced && i.errMsg != "" {
32-
return iconSyncFailed + " " + titleStr
31+
if !i.synced && i.repo.Error != nil {
32+
return errorStyle.Render(iconSyncFailed + " " + titleStr)
3333
}
3434

3535
if i.selected {
@@ -43,23 +43,25 @@ func (i item) Description() string {
4343
repo := i.repo
4444
upstream := fmt.Sprintf("%s:%s", repo.ParentFullName, repo.DefaultBranch)
4545
base := fmt.Sprintf("%s:%s", repo.FullName, repo.DefaultBranch)
46-
var msg string
4746

48-
if i.synced {
49-
msg = base + " " + "is up to date with" + " " + upstream
50-
}
51-
52-
if !i.synced && i.errMsg != "" {
53-
reason := i.errMsg
54-
msg = base + " " + "fail to sync with" + " " + upstream + fmt.Sprintf("(%s)", reason)
47+
if i.repo.Error != nil {
48+
reason := i.repo.Error.Error()
49+
msg := base + " " + "fail to sync with" + " " + upstream + fmt.Sprintf("(%s)", reason)
50+
return errorStyle.Copy().PaddingLeft(2).Render(msg)
5551
}
5652

5753
if !i.synced {
5854
upstream = fmt.Sprintf("%s:%s", repo.Parent, repo.DefaultBranch)
59-
msg = fmt.Sprintf("%s is %d commit%s behind %s", base, repo.BehindBy, mayBePlural(repo.BehindBy), upstream)
55+
msg := fmt.Sprintf("%s is %d commit%s behind %s", base, repo.BehindBy, mayBePlural(repo.BehindBy), upstream)
56+
return detailsStyle.Render(msg)
6057
}
6158

62-
return detailsStyle.Render(msg)
59+
if i.synced {
60+
msg := base + " " + "is up to date with" + " " + upstream
61+
return detailsStyle.Render(msg)
62+
}
63+
64+
return ""
6365
}
6466

6567
func (i item) FilterValue() string {

0 commit comments

Comments
 (0)