feat: initial commit
This commit is contained in:
commit
3e9c0e82da
4 changed files with 221 additions and 0 deletions
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
gogitlabber
|
||||||
13
go.mod
Normal file
13
go.mod
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
module gogitlabber
|
||||||
|
|
||||||
|
go 1.23.1
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/k0kubun/go-ansi v0.0.0-20180517002512-3bf9e2903213 // indirect
|
||||||
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
|
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db // indirect
|
||||||
|
github.com/rivo/uniseg v0.4.7 // indirect
|
||||||
|
github.com/schollz/progressbar/v3 v3.17.1 // indirect
|
||||||
|
golang.org/x/sys v0.28.0 // indirect
|
||||||
|
golang.org/x/term v0.27.0 // indirect
|
||||||
|
)
|
||||||
15
go.sum
Normal file
15
go.sum
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
github.com/k0kubun/go-ansi v0.0.0-20180517002512-3bf9e2903213 h1:qGQQKEcAR99REcMpsXCp3lJ03zYT1PkRd3kQGPn9GVg=
|
||||||
|
github.com/k0kubun/go-ansi v0.0.0-20180517002512-3bf9e2903213/go.mod h1:vNUNkEQ1e29fT/6vq2aBdFsgNPmy8qMdSay1npru+Sw=
|
||||||
|
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||||
|
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
|
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db h1:62I3jR2EmQ4l5rM/4FEfDWcRD+abF5XlKShorW5LRoQ=
|
||||||
|
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db/go.mod h1:l0dey0ia/Uv7NcFFVbCLtqEBQbrT4OCwCSKTEv6enCw=
|
||||||
|
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
||||||
|
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||||
|
github.com/schollz/progressbar/v3 v3.17.1 h1:bI1MTaoQO+v5kzklBjYNRQLoVpe0zbyRZNK6DFkVC5U=
|
||||||
|
github.com/schollz/progressbar/v3 v3.17.1/go.mod h1:RzqpnsPQNjUyIgdglUjRLgD7sVnxN1wpmBMV+UiEbL4=
|
||||||
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
|
||||||
|
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
|
golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q=
|
||||||
|
golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
|
||||||
192
main.go
Normal file
192
main.go
Normal file
|
|
@ -0,0 +1,192 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"strings"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"github.com/k0kubun/go-ansi"
|
||||||
|
"github.com/schollz/progressbar/v3"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Repository struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
PathWithNamespace string `json:"path_with_namespace"`
|
||||||
|
}
|
||||||
|
|
||||||
|
var repoDestinationPre string
|
||||||
|
var includeArchived string
|
||||||
|
var gitlabToken string
|
||||||
|
var gitlabHost string
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
|
||||||
|
// load environment variables first, they will be overridden
|
||||||
|
// by argument flags if specified.
|
||||||
|
if err := loadEnvironmentVariables(); err != nil {
|
||||||
|
log.Fatalf("Error loading environment variables: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// require at least the destination argument
|
||||||
|
if len(os.Args) <= 1 {
|
||||||
|
fmt.Println("Usage: gogitlabber --destination=<directory>")
|
||||||
|
fmt.Println("Example: gogitlabber --destination=/tmp/repos")
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// parse arguments
|
||||||
|
for _, arg := range os.Args[1:] {
|
||||||
|
switch {
|
||||||
|
|
||||||
|
case strings.HasPrefix(arg, "--destination="):
|
||||||
|
repoDestinationPre = strings.TrimPrefix(arg, "--destination=")
|
||||||
|
|
||||||
|
case strings.HasPrefix(arg, "--gitlab-api-token="):
|
||||||
|
gitlabToken = strings.TrimPrefix(arg, "--gitlab-api-token=")
|
||||||
|
|
||||||
|
case strings.HasPrefix(arg, "--gitlab-url="):
|
||||||
|
gitlabHost = strings.TrimPrefix(arg, "--gitlab-url=")
|
||||||
|
|
||||||
|
default:
|
||||||
|
fmt.Println("Usage: gogitlabber --destination=<directory>")
|
||||||
|
fmt.Println("Example: gogitlabber --destination=/tmp/repos")
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// fail if destination is unknown
|
||||||
|
if repoDestinationPre == "" {
|
||||||
|
fmt.Println("Fatal: No destination found.")
|
||||||
|
fmt.Println("Example: gogitlabber --destination=/tmp/repos")
|
||||||
|
fmt.Println("Usage: gogitlabber --destination=/tmp/repos")
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// fetch repository information
|
||||||
|
repositories, err := fetchRepositories()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Error fetching repositories: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// manage found repositories
|
||||||
|
checkoutRepositories(repositories)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func loadEnvironmentVariables() error {
|
||||||
|
gitlabToken = os.Getenv("GITLAB_API_KEY")
|
||||||
|
if gitlabToken == "" {
|
||||||
|
return fmt.Errorf("GITLAB_API_KEY environment variable is not set")
|
||||||
|
}
|
||||||
|
|
||||||
|
gitlabHost = os.Getenv("GITLAB_HOSTNAME")
|
||||||
|
if gitlabHost == "" {
|
||||||
|
return fmt.Errorf("GITLAB_HOSTNAME environment variable is not set")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func fetchRepositories() ([]Repository, error) {
|
||||||
|
|
||||||
|
archived := "archived=false"
|
||||||
|
membership := "membership=true"
|
||||||
|
perpage := "per_page=100"
|
||||||
|
order := "order_by=name"
|
||||||
|
|
||||||
|
url := fmt.Sprintf("https://%s/api/v4/projects?%s&%s&%s&%s",
|
||||||
|
gitlabHost, membership, order, archived, perpage)
|
||||||
|
|
||||||
|
req, err := http.NewRequest("GET", url, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error creating request: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
req.Header.Set("PRIVATE-TOKEN", gitlabToken)
|
||||||
|
|
||||||
|
client := &http.Client{}
|
||||||
|
resp, err := client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error making request: %v", err)
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
return nil, fmt.Errorf("API request failed with status: %d", resp.StatusCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
var repositories []Repository
|
||||||
|
if err := json.NewDecoder(resp.Body).Decode(&repositories); err != nil {
|
||||||
|
return nil, fmt.Errorf("error decoding response: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return repositories, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func checkoutRepositories(repositories []Repository) {
|
||||||
|
repoCount := len(repositories)
|
||||||
|
|
||||||
|
fmt.Printf("Found %d repositories", repoCount)
|
||||||
|
|
||||||
|
// make progressbar using:
|
||||||
|
// - github.com/k0kubun/go-ansi
|
||||||
|
// - github.com/schollz/progressbar/v3
|
||||||
|
bar := progressbar.NewOptions(
|
||||||
|
repoCount,
|
||||||
|
progressbar.OptionSetWriter(ansi.NewAnsiStdout()),
|
||||||
|
progressbar.OptionEnableColorCodes(true),
|
||||||
|
progressbar.OptionShowCount(),
|
||||||
|
progressbar.OptionSetElapsedTime(true),
|
||||||
|
progressbar.OptionSetPredictTime(false),
|
||||||
|
progressbar.OptionSetWidth(20),
|
||||||
|
progressbar.OptionSetDescription("Getting your repositories..."),
|
||||||
|
progressbar.OptionSetTheme(progressbar.Theme{
|
||||||
|
Saucer: "[green]=[reset]",
|
||||||
|
SaucerHead: "[green]>[reset]",
|
||||||
|
SaucerPadding: " ",
|
||||||
|
BarStart: "[",
|
||||||
|
BarEnd: "]",
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
for _, repo := range repositories {
|
||||||
|
|
||||||
|
repoName := string(repo.PathWithNamespace)
|
||||||
|
gitlabUrl := fmt.Sprintf("https://gitlab-token:%s@%s/%s.git",
|
||||||
|
gitlabToken, gitlabHost, repoName)
|
||||||
|
|
||||||
|
repoDestination := repoDestinationPre + repoName
|
||||||
|
|
||||||
|
cloneCmd := exec.Command("git", "clone", gitlabUrl, repoDestination)
|
||||||
|
cloneOutput, err := cloneCmd.CombinedOutput()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
|
||||||
|
// if repo already exists, try to pull the latest changes
|
||||||
|
if strings.Contains(string(cloneOutput),
|
||||||
|
"already exists and is not an empty directory") {
|
||||||
|
pullRepositories(repoDestination)
|
||||||
|
bar.Add(1)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
log.Printf("❌ Error cloning %s: %v\n%s", repoName, err, string(cloneOutput))
|
||||||
|
bar.Add(1)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
bar.Add(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func pullRepositories(repoDestination string) {
|
||||||
|
pullCmd := exec.Command("git", "-C", repoDestination, "pull", "origin")
|
||||||
|
output, err := pullCmd.CombinedOutput()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("❌ Error pulling %s: %v\n%s", repoDestination, err, string(output))
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue