package main import ( "encoding/json" "fmt" "io" "net/http" "os" "os/exec" "github.com/go-git/go-git/v6" "github.com/go-git/go-git/v6/config" gitclient "github.com/go-git/go-git/v6/plumbing/client" githttp "github.com/go-git/go-git/v6/plumbing/transport/http" "github.com/rwinkhart/go-boilerplate/back" "github.com/rwinkhart/go-boilerplate/other" ) const workingDir = "repos-new" const finishedDir = "repos" const hashName = ".gvv-hash" type giteaRespT struct { Data []struct { MirrorInterval string `json:"mirror_interval"` Language string `json:"language"` MasterURL string `json:"original_url"` Name string `json:"name"` } } func main() { // args if len(os.Args) < 3 { fmt.Println("Usage: " + os.Args[0] + " [output organization]\n\nAPI token can be piped into stdin to run non-interactively!") os.Exit(0) } baseURL := os.Args[1] apiURL := baseURL + "/api/v1/repos/search?sort=updated&order=desc&limit=999" username := os.Args[2] var organization string if len(os.Args) == 4 { organization = os.Args[3] } fmt.Print("Input token: ") token := back.ReadFromStdin() if len(token) == 0 { other.PrintError("No token provided; API token must be provided via stdin!", 1) } fmt.Print("\r \r") // start cleanup if err := os.RemoveAll(workingDir); err != nil { other.PrintError("Failed to remove "+workingDir+": "+err.Error(), 2) } // api call req, err := http.NewRequest("GET", apiURL, nil) if err != nil { other.PrintError("Failed to create request: "+err.Error(), 3) } req.Header.Set("Accept", "application/json") req.Header.Set("Authorization", "token "+string(token)) client := &http.Client{} resp, err := client.Do(req) if err != nil { other.PrintError("Failed to make request: "+err.Error(), 4) } defer resp.Body.Close() body, err := io.ReadAll(resp.Body) if err != nil { other.PrintError("Failed to read response body: "+err.Error(), 5) } // cloning and vendoring var giteaResp giteaRespT if err = json.Unmarshal(body, &giteaResp); err != nil { other.PrintError("Failed to unmarshal response: "+err.Error(), 6) } os.Mkdir(workingDir, 0755) for _, repoResp := range giteaResp.Data { if repoResp.Language == "Go" && repoResp.MirrorInterval != "" { //// clone currentRepoDir := workingDir + "/" + repoResp.Name + "-ggv" oldRepo, err := git.PlainClone(currentRepoDir, &git.CloneOptions{URL: repoResp.MasterURL, Progress: nil, Depth: 1}) if err != nil { other.PrintError("Failed to clone "+repoResp.MasterURL+": "+err.Error(), 7) } //// store commit hash head, err := oldRepo.Head() if err != nil { other.PrintError("Failed to get head of "+repoResp.MasterURL+": "+err.Error(), 8) } if err = os.WriteFile(currentRepoDir+"/"+hashName, []byte(head.Hash().String()), 0644); err != nil { other.PrintError("Failed to write head hash for "+repoResp.MasterURL+": "+err.Error(), 9) } //// vendor vendorCmd := exec.Command("go", "mod", "vendor") vendorCmd.Dir = currentRepoDir if err = vendorCmd.Run(); err != nil { other.PrintError("Failed to vendor "+repoResp.MasterURL+": "+err.Error(), 10) } //// re-init if err = os.RemoveAll(currentRepoDir + "/.git"); err != nil { other.PrintError("Failed to remove .git directory for "+repoResp.MasterURL+": "+err.Error(), 11) } newRepo, err := git.PlainInit(currentRepoDir, false) if err != nil { other.PrintError("Failed to init in "+currentRepoDir+": "+err.Error(), 12) } //// set origin if _, err = newRepo.CreateRemote(&config.RemoteConfig{ Name: "origin", URLs: []string{baseURL + "/" + organization + "/" + repoResp.Name + "-ggv.git"}, }); err != nil { other.PrintError("Failed to set origin for "+currentRepoDir+": "+err.Error(), 13) } //// add wt, err := newRepo.Worktree() if err != nil { other.PrintError("Failed to get worktree for "+currentRepoDir+": "+err.Error(), 14) } if err = wt.AddGlob("."); err != nil { other.PrintError("Failed to add files in "+currentRepoDir+": "+err.Error(), 15) } //// commit if _, err = wt.Commit("go-gitea-vendor", &git.CommitOptions{}); err != nil { other.PrintError("Failed to commit in "+currentRepoDir+": "+err.Error(), 16) } //// push if err = newRepo.Push(&git.PushOptions{ Force: true, ClientOptions: []gitclient.Option{ gitclient.WithHTTPAuth(&githttp.BasicAuth{ Username: username, Password: string(token), }), }, }); err != nil { other.PrintError("Failed to push "+currentRepoDir+": "+err.Error(), 17) } } } // end cleanup isAccessible, err := back.TargetIsFile(finishedDir, false) if isAccessible && err == nil { if err = os.RemoveAll(finishedDir + ".bak"); err != nil { other.PrintError("Failed to remove old backup dir: "+err.Error(), 18) } if err = os.Rename(finishedDir, finishedDir+".bak"); err != nil { other.PrintError("Failed to backup old repos dir: "+err.Error(), 19) } } if err = os.Rename(workingDir, finishedDir); err != nil { other.PrintError("Failed to rename new repos dir: "+err.Error(), 20) } }