fork of https://github.com/sourcegraph/zoekt
0

Configure Feed

Select the types of activity you want to include in your feed.

at main 7.8 kB View raw
1// Copyright 2016 Google Inc. All rights reserved. 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); 4// you may not use this file except in compliance with the License. 5// You may obtain a copy of the License at 6// 7// http://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12// See the License for the specific language governing permissions and 13// limitations under the License. 14 15// Command zoekt-mirror-gitea fetches all repos of a gitea user or organization 16// and clones them. It is strongly recommended to get a personal API token from 17// https://gitea.com/user/settings/applications, save the token in a file, and point 18// the --token option to it. 19package main 20 21import ( 22 "flag" 23 "fmt" 24 "log" 25 "net/url" 26 "os" 27 "path/filepath" 28 "strconv" 29 "strings" 30 31 "code.gitea.io/sdk/gitea" 32 33 "github.com/sourcegraph/zoekt/gitindex" 34) 35 36type topicsFlag []string 37 38func (f *topicsFlag) String() string { 39 return strings.Join(*f, ",") 40} 41 42func (f *topicsFlag) Set(value string) error { 43 *f = append(*f, value) 44 return nil 45} 46 47type reposFilters struct { 48 noArchived *bool 49} 50 51func main() { 52 dest := flag.String("dest", "", "destination directory") 53 giteaURL := flag.String("url", "https://gitea.com/", "Gitea url. If not set gitea.com will be used as the host.") 54 org := flag.String("org", "", "organization to mirror") 55 user := flag.String("user", "", "user to mirror") 56 token := flag.String("token", 57 filepath.Join(os.Getenv("HOME"), ".gitea-token"), 58 "file holding API token.") 59 forks := flag.Bool("forks", false, "also mirror forks.") 60 deleteRepos := flag.Bool("delete", false, "delete missing repos") 61 namePattern := flag.String("name", "", "only clone repos whose name matches the given regexp.") 62 excludePattern := flag.String("exclude", "", "don't mirror repos whose names match this regexp.") 63 topics := topicsFlag{} 64 flag.Var(&topics, "topic", "only clone repos whose have one of given topics. You can add multiple topics by setting this more than once.") 65 excludeTopics := topicsFlag{} 66 flag.Var(&excludeTopics, "exclude_topic", "don't clone repos whose have one of given topics. You can add multiple topics by setting this more than once.") 67 noArchived := flag.Bool("no_archived", false, "mirror only projects that are not archived") 68 69 flag.Parse() 70 71 if *dest == "" { 72 log.Fatal("must set --dest") 73 } 74 if *giteaURL == "" && *org == "" && *user == "" { 75 log.Fatal("must set either --org or --user when gitea.com is used as host") 76 } 77 78 var host string 79 var client *gitea.Client 80 clientOptions := []gitea.ClientOption{} 81 82 destDir := filepath.Join(*dest, host) 83 if err := os.MkdirAll(destDir, 0o755); err != nil { 84 log.Fatal(err) 85 } 86 87 if *token != "" { 88 content, err := os.ReadFile(*token) 89 if err != nil { 90 log.Fatal(err) 91 } 92 contentStr := string(content) 93 // Editors tend to insert newlines that make the token invalid, so clean it up 94 contentStr = strings.TrimSpace(contentStr) 95 clientOptions = append(clientOptions, gitea.SetToken(contentStr)) 96 } 97 client, err := gitea.NewClient(*giteaURL, clientOptions...) 98 if err != nil { 99 log.Fatal(err) 100 } 101 102 reposFilters := reposFilters{ 103 noArchived: noArchived, 104 } 105 var repos []*gitea.Repository 106 switch { 107 case *org != "": 108 log.Printf("fetch repos for org: %s", *org) 109 repos, err = getOrgRepos(client, *org, reposFilters) 110 case *user != "": 111 log.Printf("fetch repos for user: %s", *user) 112 repos, err = getUserRepos(client, *user, reposFilters) 113 default: 114 log.Printf("no user or org specified, cloning all repos.") 115 repos, err = getAllRepos(client, reposFilters) 116 } 117 118 if err != nil { 119 log.Fatal(err) 120 } 121 122 if !*forks { 123 trimmed := []*gitea.Repository{} 124 for _, r := range repos { 125 if r.Fork { 126 continue 127 } 128 trimmed = append(trimmed, r) 129 } 130 repos = trimmed 131 } 132 133 filter, err := gitindex.NewFilter(*namePattern, *excludePattern) 134 if err != nil { 135 log.Fatal(err) 136 } 137 138 { 139 trimmed := []*gitea.Repository{} 140 for _, r := range repos { 141 if !filter.Include(r.Name) { 142 log.Println(r.Name) 143 continue 144 } 145 trimmed = append(trimmed, r) 146 } 147 repos = trimmed 148 } 149 150 if err := cloneRepos(destDir, repos); err != nil { 151 log.Fatalf("cloneRepos: %v", err) 152 } 153 154 if *deleteRepos { 155 if err := deleteStaleRepos(*dest, filter, repos, *org+*user); err != nil { 156 log.Fatalf("deleteStaleRepos: %v", err) 157 } 158 } 159} 160 161func deleteStaleRepos(destDir string, filter *gitindex.Filter, repos []*gitea.Repository, user string) error { 162 var baseURL string 163 if len(repos) > 0 { 164 baseURL = repos[0].HTMLURL 165 } else { 166 return nil 167 } 168 u, err := url.Parse(baseURL) 169 if err != nil { 170 return err 171 } 172 u.Path = user 173 174 names := map[string]struct{}{} 175 for _, r := range repos { 176 u, err := url.Parse(r.HTMLURL) 177 if err != nil { 178 return err 179 } 180 181 names[filepath.Join(u.Host, u.Path+".git")] = struct{}{} 182 } 183 if err := gitindex.DeleteRepos(destDir, u, names, filter); err != nil { 184 log.Fatalf("deleteRepos: %v", err) 185 } 186 return nil 187} 188 189func filterRepositories(repos []*gitea.Repository, noArchived bool) (filteredRepos []*gitea.Repository) { 190 for _, repo := range repos { 191 if noArchived && repo.Archived { 192 continue 193 } 194 filteredRepos = append(filteredRepos, repo) 195 } 196 return 197} 198 199func searchRepos(client *gitea.Client, searchOptions *gitea.SearchRepoOptions, reposFilters reposFilters) ([]*gitea.Repository, error) { 200 var allRepos []*gitea.Repository 201 for { 202 repos, resp, err := client.SearchRepos(*searchOptions) 203 if err != nil { 204 return nil, err 205 } 206 if len(repos) == 0 { 207 break 208 } 209 210 repos = filterRepositories(repos, *reposFilters.noArchived) 211 allRepos = append(allRepos, repos...) 212 searchOptions.Page = resp.NextPage 213 if resp.NextPage == 0 { 214 break 215 } 216 } 217 218 return allRepos, nil 219} 220 221func getOrgRepos(client *gitea.Client, org string, reposFilters reposFilters) ([]*gitea.Repository, error) { 222 searchOptions := &gitea.SearchRepoOptions{} 223 // OwnerID 224 organization, _, err := client.GetOrg(org) 225 if err != nil { 226 return nil, err 227 } 228 229 searchOptions.OwnerID = organization.ID 230 231 return searchRepos(client, searchOptions, reposFilters) 232} 233 234func getAllRepos(client *gitea.Client, reposFilters reposFilters) ([]*gitea.Repository, error) { 235 return searchRepos(client, &gitea.SearchRepoOptions{}, reposFilters) 236} 237 238func getUserRepos(client *gitea.Client, user string, reposFilters reposFilters) ([]*gitea.Repository, error) { 239 searchOptions := &gitea.SearchRepoOptions{} 240 u, _, err := client.GetUserInfo(user) 241 if err != nil { 242 return nil, err 243 } 244 searchOptions.OwnerID = u.ID 245 246 return searchRepos(client, searchOptions, reposFilters) 247} 248 249func cloneRepos(destDir string, repos []*gitea.Repository) error { 250 for _, r := range repos { 251 host, err := url.Parse(r.HTMLURL) 252 if err != nil { 253 return err 254 } 255 log.Printf("cloning %s", r.HTMLURL) 256 257 config := map[string]string{ 258 "zoekt.web-url-type": "gitea", 259 "zoekt.web-url": r.HTMLURL, 260 "zoekt.name": filepath.Join(host.Hostname(), r.FullName), 261 262 "zoekt.gitea-stars": strconv.Itoa(r.Stars), 263 "zoekt.gitea-watchers": strconv.Itoa(r.Watchers), 264 "zoekt.gitea-subscribers": strconv.Itoa(r.Watchers), // FIXME: Get repo subscribers from API 265 "zoekt.gitea-forks": strconv.Itoa(r.Forks), 266 267 "zoekt.archived": marshalBool(r.Archived), 268 "zoekt.fork": marshalBool(r.Fork), 269 "zoekt.public": marshalBool(r.Private || r.Internal), // count internal repos as private 270 } 271 dest, err := gitindex.CloneRepo(destDir, r.FullName, r.CloneURL, config) 272 if err != nil { 273 return err 274 } 275 if dest != "" { 276 fmt.Println(dest) 277 } 278 279 } 280 281 return nil 282} 283 284func marshalBool(b bool) string { 285 if b { 286 return "1" 287 } 288 return "0" 289}