6
6
"log"
7
7
"net/http"
8
8
"sort"
9
+ "sync"
9
10
10
11
"github.com/google/go-github/v52/github"
11
12
)
@@ -26,65 +27,31 @@ type RepositoryWithDetails struct {
26
27
ParentDeleted bool
27
28
Private bool
28
29
BehindBy int
30
+ Error error
29
31
}
30
32
31
33
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 )
34
36
35
37
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 )
37
39
}
38
40
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 )
74
42
75
- if cmpr .GetBehindBy () < 1 {
43
+ for fork := range forkStream {
44
+ if fork .Error == nil && fork .BehindBy < 1 {
76
45
continue
77
46
}
78
-
79
- forks = append (forks , buildDetails (repo , cmpr , resp .StatusCode ))
80
-
47
+ forksWithDetails = append (forksWithDetails , fork )
81
48
}
82
49
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
85
52
})
86
53
87
- return forks , nil
54
+ return forksWithDetails , nil
88
55
}
89
56
90
57
func SyncBranchWithUpstreamRepo (client * github.Client , repo * RepositoryWithDetails ) error {
@@ -102,40 +69,109 @@ func SyncBranchWithUpstreamRepo(client *github.Client, repo *RepositoryWithDetai
102
69
return nil
103
70
}
104
71
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
+
105
137
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 ()
108
144
}
109
145
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 ()
122
156
}
157
+
158
+ return repoWithDetails
123
159
}
124
160
125
- func getAllRepositories (ctx context.Context , client * github.Client ) ([]* github.Repository , error ) {
161
+ func getAllForks (ctx context.Context , client * github.Client ) ([]* github.Repository , error ) {
126
162
var allRepos []* github.Repository
127
163
opts := & github.RepositoryListOptions {
128
164
Type : "owner" ,
129
165
ListOptions : github.ListOptions {PerPage : pageSize },
130
166
}
131
167
132
168
for {
133
- repos , resp , err := client .Repositories .List (ctx , "" , opts )
169
+ forks , resp , err := client .Repositories .List (ctx , "" , opts )
134
170
if err != nil {
135
171
return allRepos , err
136
172
}
137
173
138
- allRepos = append (allRepos , repos ... )
174
+ allRepos = append (allRepos , forks ... )
139
175
140
176
if resp .NextPage == 0 {
141
177
break
@@ -144,5 +180,14 @@ func getAllRepositories(ctx context.Context, client *github.Client) ([]*github.R
144
180
opts .Page = resp .NextPage
145
181
}
146
182
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
148
193
}
0 commit comments