~hokiegeek/gitlab-to-srht

3e1829ffb604ff0d199adb90c042197305d774fb — HokieGeek 3 months ago
Listing GL repos
1 files changed, 347 insertions(+), 0 deletions(-)

A main.go
A  => main.go +347 -0
@@ 1,347 @@
package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"net/http"
	"net/http/httputil"
	"os"
	"os/exec"
	"regexp"
	"strings"
	"time"
	// "gopkg.in/src-d/go-git.v4"
)

type gitlabProjectResponse []gitlabProject

type gitlabProject struct {
	ID                                        int64        `json:"id"`
	Description                               string       `json:"description"`
	DefaultBranch                             string       `json:"default_branch"`
	Visibility                                string       `json:"visibility"`
	SSHURLToRepo                              string       `json:"ssh_url_to_repo"`
	HTTPURLToRepo                             string       `json:"http_url_to_repo"`
	WebURL                                    string       `json:"web_url"`
	ReadmeURL                                 string       `json:"readme_url"`
	TagList                                   []string     `json:"tag_list"`
	Owner                                     owner        `json:"owner"`
	Name                                      string       `json:"name"`
	NameWithNamespace                         string       `json:"name_with_namespace"`
	Path                                      string       `json:"path"`
	PathWithNamespace                         string       `json:"path_with_namespace"`
	IssuesEnabled                             bool         `json:"issues_enabled"`
	OpenIssuesCount                           int64        `json:"open_issues_count"`
	MergeRequestsEnabled                      bool         `json:"merge_requests_enabled"`
	JobsEnabled                               bool         `json:"jobs_enabled"`
	WikiEnabled                               bool         `json:"wiki_enabled"`
	SnippetsEnabled                           bool         `json:"snippets_enabled"`
	ResolveOutdatedDiffDiscussions            bool         `json:"resolve_outdated_diff_discussions"`
	ContainerRegistryEnabled                  bool         `json:"container_registry_enabled"`
	CreatedAt                                 string       `json:"created_at"`
	LastActivityAt                            string       `json:"last_activity_at"`
	CreatorID                                 int64        `json:"creator_id"`
	Namespace                                 namespace    `json:"namespace"`
	ImportStatus                              string       `json:"import_status"`
	Archived                                  bool         `json:"archived"`
	AvatarURL                                 *string      `json:"avatar_url"`
	SharedRunnersEnabled                      bool         `json:"shared_runners_enabled"`
	ForksCount                                int64        `json:"forks_count"`
	StarCount                                 int64        `json:"star_count"`
	RunnersToken                              string       `json:"runners_token"`
	CiDefaultGitDepth                         int64        `json:"ci_default_git_depth"`
	PublicJobs                                bool         `json:"public_jobs"`
	SharedWithGroups                          []string     `json:"shared_with_groups"`
	OnlyAllowMergeIfPipelineSucceeds          bool         `json:"only_allow_merge_if_pipeline_succeeds"`
	OnlyAllowMergeIfAllDiscussionsAreResolved bool         `json:"only_allow_merge_if_all_discussions_are_resolved"`
	RemoveSourceBranchAfterMerge              bool         `json:"remove_source_branch_after_merge"`
	RequestAccessEnabled                      bool         `json:"request_access_enabled"`
	MergeMethod                               string       `json:"merge_method"`
	Statistics                                statistics   `json:"statistics"`
	Links                                     links        `json:"_links"`
	ImportError                               string       `json:"import_error"`
	Permissions                               *permissions `json:"permissions,omitempty"`
}

type links struct {
	Self          string `json:"self"`
	Issues        string `json:"issues"`
	MergeRequests string `json:"merge_requests"`
	RepoBranches  string `json:"repo_branches"`
	Labels        string `json:"labels"`
	Events        string `json:"events"`
	Members       string `json:"members"`
}

type namespace struct {
	ID       int64  `json:"id"`
	Name     string `json:"name"`
	Path     string `json:"path"`
	Kind     string `json:"kind"`
	FullPath string `json:"full_path"`
}

type owner struct {
	ID        int64  `json:"id"`
	Name      string `json:"name"`
	CreatedAt string `json:"created_at"`
}

type permissions struct {
	ProjectAccess access `json:"project_access"`
	GroupAccess   access `json:"group_access"`
}

type access struct {
	AccessLevel       int64 `json:"access_level"`
	NotificationLevel int64 `json:"notification_level"`
}

type statistics struct {
	CommitCount      int64 `json:"commit_count"`
	StorageSize      int64 `json:"storage_size"`
	RepositorySize   int64 `json:"repository_size"`
	WikiSize         int64 `json:"wiki_size"`
	LFSObjectsSize   int64 `json:"lfs_objects_size"`
	JobArtifactsSize int64 `json:"job_artifacts_size"`
	PackagesSize     int64 `json:"packages_size"`
}

type srhtCreateRepo struct {
	Name        string `json:"name"`
	Description string `json:"description,omitempty"`
	Visibility  string `json:"visibility,omitempty"`
}

// func req(method, url, token, token_header string, payload io.Reader) (*http.Response, error) {
func req(request *http.Request, payload io.Reader) (*http.Response, error) {
	if dump, err := httputil.DumpRequest(request, true); err == nil {
		fmt.Printf("REQUEST: %q\n", dump)
	}
	/*
		log.Printf("TRACE: req(%s, %s, %s, %s, payload)", method, url, token, token_header)
		request, err := http.NewRequest(method, url, payload)
		if err != nil {
			return nil, fmt.Errorf("could not create HTTP request: %v", err)
		}

		request.Header.Set(token_header, token)
	*/

	if payload != nil {
		request.Header.Set("Content-Type", "application/json")
	}

	client := &http.Client{
		Timeout: 60 * time.Second,
	}

	return client.Do(request)
}

func glreq(method, url, token string, payload io.Reader) (*http.Response, error) {
	// url := fmt.Sprintf("https://gitlab.com/api/v4/%s", endpoint)
	// header := fmt.Sprintf("PRIVATE-TOKEN: %s'", token)
	request, err := http.NewRequest(method, url, payload)
	if err != nil {
		return nil, fmt.Errorf("could not create HTTP request: %v", err)
	}
	request.Header.Set("PRIVATE-TOKEN", token)
	return req(request, payload)
}

func srhtreq(method, endpoint, token string, payload io.Reader) (*http.Response, error) {
	url := fmt.Sprintf("https://git.sr.ht/%s", endpoint)
	// header := fmt.Sprintf("Authorization:'token %s'", token)
	/*
		tkn := fmt.Sprintf("'token %s'", token)
		request, err := http.NewRequest(method, url, payload)
		if err != nil {
			return nil, fmt.Errorf("could not create HTTP request: %v", err)
		}
	*/
	// return req(method, url, tkn, "Authorization", payload)
	request, err := http.NewRequest(method, url, payload)
	if err != nil {
		return nil, fmt.Errorf("could not create HTTP request: %v", err)
	}
	// request.SetBasicAuth("token", token)
	request.Header.Set("Authorization", fmt.Sprintf("token %s", token))
	return req(request, payload)
}

func listGitlabRepos(user, token string) ([]gitlabProject, error) {
	endpoint := fmt.Sprintf("https://gitlab.com/api/v4/users/%s/projects", user)
	var repos []gitlabProject

	linkRe := regexp.MustCompile(`<([^>]*)>;\s*rel="([^"]*)"`)

	getLink := func(str, rel string) string {
		links := strings.Split(str, ",")
		for _, link := range links {
			m := linkRe.FindStringSubmatch(link)
			if m[2] == rel {
				return m[1]
			}
		}
		return ""
	}

	getPage := func(endpoint string) ([]gitlabProject, string, error) {
		resp, err := glreq(http.MethodGet, endpoint, token, nil)
		if err != nil {
			return nil, "", err
		}

		if resp.StatusCode != http.StatusOK {
			return nil, "", fmt.Errorf("did not get OK status: %s", resp.Status)
		}

		defer resp.Body.Close()
		body, err := ioutil.ReadAll(resp.Body)
		if err != nil {
			return nil, "", err
		}

		var reposPage []gitlabProject
		if err := json.Unmarshal(body, &reposPage); err != nil {
			return nil, "", err
		}

		var next string
		if link, ok := resp.Header["Link"]; ok {
			// fmt.Printf("%v\n", link[0])
			// fmt.Printf("Next: %s\n", getLink(link[0], "next"))
			next = getLink(link[0], "next")
		}

		return reposPage, next, nil
	}

	for {
		page, next, err := getPage(endpoint)
		if err != nil {
			return nil, err
		}

		repos = append(repos, page...)

		if next == "" {
			break
		}

		endpoint = next
	}

	return repos, nil
}

func createSrhtRepo(token, name, description string) error {
	req := srhtCreateRepo{name, description, "public"}
	buf, err := json.Marshal(req)
	if err != nil {
		return fmt.Errorf("could not create request: %s", err)
	}

	resp, err := srhtreq(http.MethodPost, "/api/repos", token, bytes.NewBuffer(buf))
	if err != nil {
		return err
	}

	if resp.StatusCode != http.StatusCreated {
		return fmt.Errorf("did not get Created status: %s", resp.Status)
	}

	return nil
}

func pushToSrht(glURL, srhtURL string) error {
	/*
		glURL := "https://gitlab.com/HokieGeek/goxbm.git"
		srhtURL := "https://git.sr.ht/~hokiegeek/goxbm"

		r, err := git.Clone(memory.NewStorage(), nil, &git.CloneOptions{URL: glURL})

		// Add a new remote, with the default fetch refspec
		_, err := r.CreateRemote(&config.RemoteConfig{
			Name: "srht",
			URLs: []string{srhtURL},
		})

		err := r.Push(&git.PushOptions{
			RemoteName: "srht",
			RefSpecs:   []string{"master"},
		})
	*/

	dir, err := ioutil.TempFile("", "gl2srht")
	if err != nil {
		log.Fatal(err)
	}
	defer os.Remove(dir.Name())

	// Clone
	cmd := exec.Command("git", "clone", glURL, dir.Name())
	cmd.Run()
	// if err := cmd.Run(); err != nil {
	// 	fmt.Println(dir.Name())
	// 	fmt.Println(cmd.Output())
	// 	return err
	// }

	// Add remote
	cmd = exec.Command("git", fmt.Sprintf("--git-dir=%s/.git", dir.Name()), fmt.Sprintf("--work-tree=%s", dir.Name()), "remote", "add", "srht", srhtURL)
	cmd.Run()
	// if err := cmd.Run(); err != nil {
	// 	fmt.Println(dir.Name())
	// 	fmt.Println(cmd.Output())
	// 	return err
	// }

	// Push
	// cmd = exec.Command("git", "--git-dir=%s/.git --work-tree=%s push --all srht", dir.Name(), dir.Name()))
	cmd = exec.Command("git", fmt.Sprintf("--git-dir=%s/.git", dir.Name()), fmt.Sprintf("--work-tree=%s", dir.Name()), "push", "--all", "srht")
	cmd.Run()
	// if err := cmd.Run(); err != nil {
	// 	fmt.Println(cmd.Output())
	// 	return err
	// }

	return nil
}

func main() {
	gitlab := strings.Split(os.Args[1], ":")
	srht := strings.Split(os.Args[2], ":")

	fmt.Println(gitlab)
	fmt.Println(srht)

	glrepos, err := listGitlabRepos(gitlab[0], gitlab[1])
	if err != nil {
		panic(err)
	}

	for _, r := range glrepos {
		// fmt.Println(r.Name, r.Description, r.HTTPURLToRepo)
		fmt.Println(r.Name, r.HTTPURLToRepo)
	}

	err = createSrhtRepo(srht[1], "goxbm", "TESTING")
	if err != nil {
		panic(err)
	}

	glURL := "https://gitlab.com/HokieGeek/goxbm.git"
	srhtURL := "https://git.sr.ht/~hokiegeek/goxbm"
	if err := pushToSrht(glURL, srhtURL); err != nil {
		if _, er := srhtreq(http.MethodDelete, "/api/repos/goxbm", srht[1], nil); er != nil {
			fmt.Printf("%v", er)
		}
		panic(err)
	}
}