mirror of
https://github.com/rwinkhart/go-gitea-vendor.git
synced 2026-08-28 12:56:36 -04:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5609e30952 | ||
|
|
9c3cfb0705 | ||
|
|
06ae5291ba | ||
|
|
d851259309 | ||
|
|
113705f892 | ||
|
|
6db5922be8 | ||
|
|
b33e4164fd |
@@ -10,7 +10,9 @@ ENABLE_PUSH_CREATE_USER = true
|
||||
ENABLE_PUSH_CREATE_ORG = true
|
||||
```
|
||||
|
||||
Optionally, create a new organization in your Gitea instance to store your `ggv` vendor repos.
|
||||
Create a new organization in your Gitea instance to store your `ggv` vendor repos.
|
||||
This is necessary not just to keep your Gitea clean, but also to tell `ggv` what repos to ignore as inputs
|
||||
(you don't want to vendor your vendor repos!).
|
||||
|
||||
> [!WARNING]
|
||||
>It is recommended to create a user that only has access to that one organization and use said new user to generate the below API key.
|
||||
@@ -22,7 +24,7 @@ This key needs `repository` "Read and Write" access. Everything else can be left
|
||||
## Usage
|
||||
`echo 'EXAMPLE_API_TOKEN' | ggv https://gitea.example.com myOutputOrganization`
|
||||
|
||||
I recommend setting up a chron job or some form of automation for this. My deployment involves running this on my Gitea mirror (this server mirrors my main Gitea instance as well as my GitHub) weekly using a chron job, then my main Gitea instance mirrors the output repos from this tool.
|
||||
I recommend setting up a cron job or some form of automation for this.
|
||||
|
||||
> [!WARNING]
|
||||
>This tool assumes your default branch is what you want to vendor.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
module ggv
|
||||
|
||||
go 1.26.4
|
||||
go 1.26.5
|
||||
|
||||
require (
|
||||
github.com/go-git/go-git/v6 v6.0.0-alpha.4
|
||||
@@ -10,16 +10,16 @@ require (
|
||||
require (
|
||||
github.com/Microsoft/go-winio v0.6.2 // indirect
|
||||
github.com/ProtonMail/go-crypto v1.4.1 // indirect
|
||||
github.com/cloudflare/circl v1.6.3 // indirect
|
||||
github.com/cloudflare/circl v1.6.4 // indirect
|
||||
github.com/emirpasic/gods v1.18.1 // indirect
|
||||
github.com/go-git/gcfg/v2 v2.0.2 // indirect
|
||||
github.com/go-git/go-billy/v6 v6.0.0-alpha.1 // indirect
|
||||
github.com/kevinburke/ssh_config v1.6.0 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.4.0 // indirect
|
||||
github.com/pjbgf/sha1cd v0.6.0 // indirect
|
||||
github.com/sergi/go-diff v1.4.0 // indirect
|
||||
golang.org/x/crypto v0.51.0 // indirect
|
||||
golang.org/x/net v0.54.0 // indirect
|
||||
golang.org/x/sync v0.20.0 // indirect
|
||||
golang.org/x/sys v0.44.0 // indirect
|
||||
golang.org/x/crypto v0.53.0 // indirect
|
||||
golang.org/x/net v0.56.0 // indirect
|
||||
golang.org/x/sync v0.21.0 // indirect
|
||||
golang.org/x/sys v0.46.0 // indirect
|
||||
)
|
||||
|
||||
@@ -6,8 +6,8 @@ github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFI
|
||||
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4=
|
||||
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
|
||||
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
|
||||
github.com/cloudflare/circl v1.6.3 h1:9GPOhQGF9MCYUeXyMYlqTR6a5gTrgR/fBLXvUgtVcg8=
|
||||
github.com/cloudflare/circl v1.6.3/go.mod h1:2eXP6Qfat4O/Yhh8BznvKnJ+uzEoTQ6jVKJRn81BiS4=
|
||||
github.com/cloudflare/circl v1.6.4 h1:pOXuDTCEYyzydgUpQ0CQz3LsinKjiSk6nNP5Lt5K64U=
|
||||
github.com/cloudflare/circl v1.6.4/go.mod h1:YxarevkLlbaHuWsxG6vmYNWBEsSp4pnp7j+4VljMavY=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
@@ -25,8 +25,8 @@ github.com/go-git/go-git/v6 v6.0.0-alpha.4 h1:aDTc2UGanmaE7FkGLSlBEB9nohMnQ+RKXc
|
||||
github.com/go-git/go-git/v6 v6.0.0-alpha.4/go.mod h1:4ODa/G7hPWrh4Y+7lmt59Ij3zW38IEfvRoAZxLYYBhc=
|
||||
github.com/kevinburke/ssh_config v1.6.0 h1:J1FBfmuVosPHf5GRdltRLhPJtJpTlMdKTBjRgTaQBFY=
|
||||
github.com/kevinburke/ssh_config v1.6.0/go.mod h1:q2RIzfka+BXARoNexmF9gkxEX7DmvbW9P4hIVx2Kg4M=
|
||||
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
|
||||
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
|
||||
github.com/klauspost/cpuid/v2 v2.4.0 h1:S6Hrbc7+ywsr0r+RLapfGBHfyefhCTwEh3A0tV913Dw=
|
||||
github.com/klauspost/cpuid/v2 v2.4.0/go.mod h1:19jmZ9mjzoF//ddRSUsv0zfBTJWh3QJh9FNxZTMrGxU=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
@@ -42,18 +42,18 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||
golang.org/x/crypto v0.51.0 h1:IBPXwPfKxY7cWQZ38ZCIRPI50YLeevDLlLnyC5wRGTI=
|
||||
golang.org/x/crypto v0.51.0/go.mod h1:8AdwkbraGNABw2kOX6YFPs3WM22XqI4EXEd8g+x7Oc8=
|
||||
golang.org/x/net v0.54.0 h1:2zJIZAxAHV/OHCDTCOHAYehQzLfSXuf/5SoL/Dv6w/w=
|
||||
golang.org/x/net v0.54.0/go.mod h1:Sj4oj8jK6XmHpBZU/zWHw3BV3abl4Kvi+Ut7cQcY+cQ=
|
||||
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
|
||||
golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
|
||||
golang.org/x/sys v0.44.0 h1:ildZl3J4uzeKP07r2F++Op7E9B29JRUy+a27EibtBTQ=
|
||||
golang.org/x/sys v0.44.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||
golang.org/x/term v0.43.0 h1:S4RLU2sB31O/NCl+zFN9Aru9A/Cq2aqKpTZJ6B+DwT4=
|
||||
golang.org/x/term v0.43.0/go.mod h1:lrhlHNdQJHO+1qVYiHfFKVuVioJIheAc3fBSMFYEIsk=
|
||||
golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc=
|
||||
golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38=
|
||||
golang.org/x/crypto v0.53.0 h1:QZ4Muo8THX6CizN2vPPd5fBGHyogrdK9fG4wLPFUsto=
|
||||
golang.org/x/crypto v0.53.0/go.mod h1:DNLU434OwVakk9PzuwV8w62mAJpRJL3vsgcfp4Qnsio=
|
||||
golang.org/x/net v0.56.0 h1:Rw8j/hFzGvJUZwNBXnAtf5sVDVt+65SK2C7IxCxZt5o=
|
||||
golang.org/x/net v0.56.0/go.mod h1:D3Ku6r+V6JROoZK144D2XfMHFcMq/0zSfLelVTCFKec=
|
||||
golang.org/x/sync v0.21.0 h1:HLII4xRRTtCRkxYp4HNFF0Js/Og6q2i++KXbg0gHCwM=
|
||||
golang.org/x/sync v0.21.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
|
||||
golang.org/x/sys v0.46.0 h1:noSf2Fq6F8DBgS+LysIkx7rIExoNHJsxOAtPp4rthXw=
|
||||
golang.org/x/sys v0.46.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||
golang.org/x/term v0.44.0 h1:0rLvDRCtNj0gZkyIXhCyOb2OAzEhLVqc4B+hrsBhrmc=
|
||||
golang.org/x/term v0.44.0/go.mod h1:7ze4MdzUzLXpSAoFP1H0bOI9aXDqveSvatT5vKcFh2Y=
|
||||
golang.org/x/text v0.38.0 h1:sXmwo9DwP3OK9EZ7PqAdaooSGozfl/3a6/xJcbzPRhE=
|
||||
golang.org/x/text v0.38.0/go.mod h1:YXZt3QhHUKYT53r2lLKFIVi6Ao1jdzrTR/KQ09qyxF4=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
|
||||
@@ -8,11 +8,13 @@ import (
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/go-git/go-git/v6"
|
||||
"github.com/go-git/go-git/v6/config"
|
||||
"github.com/go-git/go-git/v6/plumbing"
|
||||
gitclient "github.com/go-git/go-git/v6/plumbing/client"
|
||||
"github.com/go-git/go-git/v6/plumbing/object"
|
||||
githttp "github.com/go-git/go-git/v6/plumbing/transport/http"
|
||||
"github.com/go-git/go-git/v6/storage/memory"
|
||||
"github.com/rwinkhart/go-boilerplate/back"
|
||||
@@ -25,25 +27,28 @@ 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"`
|
||||
Owner struct {
|
||||
Organization string `json:"username"`
|
||||
} `json:"owner"`
|
||||
Language string `json:"language"`
|
||||
MasterURL string `json:"original_url"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
}
|
||||
|
||||
func logError(message string) {
|
||||
fmt.Println(back.AnsiWarning + message + back.AnsiReset)
|
||||
}
|
||||
|
||||
func main() {
|
||||
// args
|
||||
if len(os.Args) < 2 {
|
||||
fmt.Println("Usage: " + os.Args[0] + " <base url> [output organization]\n\nAPI token can be piped into stdin to run non-interactively!")
|
||||
if len(os.Args) < 3 {
|
||||
fmt.Println("Usage: " + os.Args[0] + " <base url> <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=asc&limit=999"
|
||||
var organization string
|
||||
if len(os.Args) == 3 {
|
||||
organization = os.Args[2]
|
||||
}
|
||||
outputOrganization := os.Args[2]
|
||||
fmt.Print("Input token: ")
|
||||
token := back.ReadFromStdin()
|
||||
if len(token) == 0 {
|
||||
@@ -82,7 +87,7 @@ func main() {
|
||||
os.Mkdir(workingDir, 0755)
|
||||
repoLoop:
|
||||
for _, repoResp := range giteaResp.Data {
|
||||
if repoResp.Language == "Go" && repoResp.MirrorInterval != "" {
|
||||
if repoResp.Owner.Organization != outputOrganization && repoResp.Language == "Go" {
|
||||
currentRepoDir := workingDir + "/" + repoResp.Name + "-ggv"
|
||||
//// skip if local copy is already up-to-date
|
||||
targetRepoDir := finishedDir + "/" + repoResp.Name + "-ggv"
|
||||
@@ -101,10 +106,11 @@ repoLoop:
|
||||
if strings.TrimSpace(string(localHash)) == ref2.Hash().String() {
|
||||
fmt.Println("Skipping " + repoResp.MasterURL + " (already up-to-date)")
|
||||
if err = os.MkdirAll(currentRepoDir, 0755); err != nil {
|
||||
other.PrintError("Failed to create dummy repo for "+repoResp.MasterURL+": "+err.Error(), 7)
|
||||
logError("Failed to create dummy repo for " + repoResp.MasterURL + ": " + err.Error())
|
||||
continue repoLoop
|
||||
}
|
||||
if err = os.WriteFile(currentRepoDir+"/"+hashName, localHash, 0644); err != nil {
|
||||
other.PrintError("Failed to write dummy head hash for "+repoResp.MasterURL+": "+err.Error(), 8)
|
||||
logError("Failed to write dummy head hash for " + repoResp.MasterURL + ": " + err.Error())
|
||||
}
|
||||
continue repoLoop
|
||||
}
|
||||
@@ -114,53 +120,66 @@ repoLoop:
|
||||
break
|
||||
}
|
||||
}
|
||||
} else {
|
||||
logError("Failed to fetch remote refs for " + repoResp.MasterURL + ": " + err.Error())
|
||||
continue repoLoop
|
||||
}
|
||||
}
|
||||
//// clone
|
||||
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)
|
||||
logError("Failed to clone " + repoResp.MasterURL + ": " + err.Error())
|
||||
continue repoLoop
|
||||
}
|
||||
//// store commit hash
|
||||
head, err := oldRepo.Head()
|
||||
if err != nil {
|
||||
other.PrintError("Failed to get head of "+repoResp.MasterURL+": "+err.Error(), 8)
|
||||
logError("Failed to get head of " + repoResp.MasterURL + ": " + err.Error())
|
||||
continue repoLoop
|
||||
}
|
||||
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)
|
||||
logError("Failed to write head hash for " + repoResp.MasterURL + ": " + err.Error())
|
||||
continue repoLoop
|
||||
}
|
||||
//// 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)
|
||||
logError("Failed to vendor " + repoResp.MasterURL + ": " + err.Error())
|
||||
continue repoLoop
|
||||
}
|
||||
//// re-init
|
||||
if err = os.RemoveAll(currentRepoDir + "/.git"); err != nil {
|
||||
other.PrintError("Failed to remove .git directory for "+repoResp.MasterURL+": "+err.Error(), 11)
|
||||
logError("Failed to remove .git directory for " + repoResp.MasterURL + ": " + err.Error())
|
||||
continue repoLoop
|
||||
}
|
||||
newRepo, err := git.PlainInit(currentRepoDir, false)
|
||||
if err != nil {
|
||||
other.PrintError("Failed to init in "+currentRepoDir+": "+err.Error(), 12)
|
||||
logError("Failed to init in " + currentRepoDir + ": " + err.Error())
|
||||
continue repoLoop
|
||||
}
|
||||
//// set origin
|
||||
if _, err = newRepo.CreateRemote(&config.RemoteConfig{
|
||||
Name: "origin",
|
||||
URLs: []string{baseURL + "/" + organization + "/" + repoResp.Name + "-ggv.git"},
|
||||
URLs: []string{baseURL + "/" + outputOrganization + "/" + repoResp.Name + "-ggv.git"},
|
||||
}); err != nil {
|
||||
other.PrintError("Failed to set origin for "+currentRepoDir+": "+err.Error(), 13)
|
||||
logError("Failed to set origin for " + currentRepoDir + ": " + err.Error())
|
||||
continue repoLoop
|
||||
}
|
||||
//// add
|
||||
wt, err := newRepo.Worktree()
|
||||
if err != nil {
|
||||
other.PrintError("Failed to get worktree for "+currentRepoDir+": "+err.Error(), 14)
|
||||
logError("Failed to get worktree for " + currentRepoDir + ": " + err.Error())
|
||||
continue repoLoop
|
||||
}
|
||||
if err = wt.AddGlob("."); err != nil {
|
||||
other.PrintError("Failed to add files in "+currentRepoDir+": "+err.Error(), 15)
|
||||
logError("Failed to add files in " + currentRepoDir + ": " + err.Error())
|
||||
continue repoLoop
|
||||
}
|
||||
//// commit
|
||||
if _, err = wt.Commit("go-gitea-vendor", &git.CommitOptions{}); err != nil {
|
||||
other.PrintError("Failed to commit in "+currentRepoDir+": "+err.Error(), 16)
|
||||
if _, err = wt.Commit("go-gitea-vendor", &git.CommitOptions{Author: &object.Signature{Name: "ggv", Email: "gvv@local.local", When: time.Now()}}); err != nil {
|
||||
logError("Failed to commit in " + currentRepoDir + ": " + err.Error())
|
||||
continue repoLoop
|
||||
}
|
||||
//// push
|
||||
if err = newRepo.Push(&git.PushOptions{
|
||||
@@ -172,16 +191,17 @@ repoLoop:
|
||||
}),
|
||||
},
|
||||
}); err != nil {
|
||||
other.PrintError("Failed to push "+currentRepoDir+": "+err.Error(), 17)
|
||||
logError("Failed to push " + currentRepoDir + ": " + err.Error())
|
||||
continue repoLoop
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// end cleanup
|
||||
if err = os.RemoveAll(finishedDir); err != nil {
|
||||
other.PrintError("Failed to remove old repos dir: "+err.Error(), 18)
|
||||
other.PrintError("Failed to remove old repos dir: "+err.Error(), 7)
|
||||
}
|
||||
if err = os.Rename(workingDir, finishedDir); err != nil {
|
||||
other.PrintError("Failed to rename new repos dir: "+err.Error(), 20)
|
||||
other.PrintError("Failed to rename new repos dir: "+err.Error(), 8)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user