commit 926aeb4eee77c5ea211ff4d3bc76d23252310950 Author: Gardient Date: Thu Jun 7 22:50:47 2018 +0300 initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b84318c --- /dev/null +++ b/.gitignore @@ -0,0 +1,34 @@ +.env +debug + +# Created by https://www.gitignore.io/api/visualstudiocode + +### VisualStudioCode ### +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +.history + + +# End of https://www.gitignore.io/api/visualstudiocode + +# Created by https://www.gitignore.io/api/go + +### Go ### +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib + +# Test binary, build with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out + + +# End of https://www.gitignore.io/api/go diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..83c91d9 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,21 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "Launch", + "type": "go", + "request": "launch", + "mode": "debug", + "remotePath": "", + "port": 2345, + "host": "127.0.0.1", + "program": "${workspaceFolder}/main.go", + "env": {}, + "args": [], + "showLog": true + } + ] +} diff --git a/main.go b/main.go new file mode 100644 index 0000000..7396626 --- /dev/null +++ b/main.go @@ -0,0 +1,69 @@ +package main + +import ( + "fmt" + "net/http" + "os" + "strings" + + "git.gyulakerezsi.ro/migrate-github/migration" + "git.gyulakerezsi.ro/migrate-github/utils" + "github.com/joho/godotenv" +) + +type ghRepo struct { + Name string `json:"name"` + FullName string `json:"full_name"` + CloneURL string `json:"clone_url"` + Description string `json:"description"` +} + +func main() { + godotenv.Load() + + gogsTrgt := os.Getenv("TARGET_GOGS") + gogsPat := os.Getenv("GOGS_PAT") + user := os.Getenv("GH_USER") + + response, err := http.Get("https://api.github.com/users/" + user + "/repos") + utils.PanicOnError(err) + + migrationClient := migration.NewClient(gogsTrgt, gogsPat) + + repos := make([]ghRepo, 0) + utils.UnmarshalFromResponse(response, &repos) + + migrated := make([]string, 0) + for _, el := range repos { + fmt.Printf("Migrate %s to your git? [Y/n] > ", el.FullName) + resp := utils.ReadLine() + + switch resp = strings.ToLower(strings.TrimSpace(resp)); resp { + case "n": + fmt.Println("skipping") + default: + fmt.Print("Target repo name [leave blank to use the curent one] > ") + + newName := utils.ReadLine() + if newName == "" { + newName = el.Name + } + + fmt.Printf("migrating %s ==> %s/%s\n", el.CloneURL, migrationClient.User.Username, newName) + err := migrationClient.Migrate(el.CloneURL, el.Description, newName) + utils.PanicOnError(err) + + migrated = append(migrated, el.FullName+" -> "+migrationClient.User.Username+"/"+newName) + } + } + + fmt.Println("\n\n================================================") + if len(migrated) > 0 { + fmt.Println("Migrated:") + for _, el := range migrated { + fmt.Printf("\t%s\n", el) + } + } else { + fmt.Println("Nothing migrated") + } +} diff --git a/migration/client.go b/migration/client.go new file mode 100644 index 0000000..8cdafe4 --- /dev/null +++ b/migration/client.go @@ -0,0 +1,83 @@ +package migration + +import ( + "bytes" + "encoding/json" + "fmt" + "net/http" + "strings" + + "git.gyulakerezsi.ro/migrate-github/utils" +) + +type client struct { + apiURL string + pat string + httpClient *http.Client + User *userResponse +} + +type userResponse struct { + Username string `json:"username"` + ID int `json:"id"` +} + +type migrateRequest struct { + CloneAddr string `json:"clone_addr"` + UID int `json:"uid"` + RepoName string `json:"repo_name"` + Description string `json:"description"` +} + +// Migrate will send a migrate request to the gogs server +func (c *client) Migrate(cloneURL, description, targetName string) error { + jsonBody, err := json.Marshal(&migrateRequest{cloneURL, c.User.ID, targetName, description}) + utils.PanicOnError(err) + + req, err := http.NewRequest(http.MethodPost, c.apiURL+"repos/migrate", bytes.NewReader(jsonBody)) + utils.PanicOnError(err) + + c.setHeadersOnRequest(req) + + resp, err := c.httpClient.Do(req) + utils.PanicOnError(err) + + if resp.StatusCode < 200 || resp.StatusCode >= 300 { + return fmt.Errorf("got non-success status code: %s\n==========request=========\n%+v\n=================response=============\n%+v", resp.Status, req, resp) + } + + return nil +} + +func (c *client) setHeadersOnRequest(req *http.Request) { + req.Header.Set("Authorization", "token "+c.pat) + req.Header.Set("Accept", "application/json") + + if req.Method == http.MethodPost { + req.Header.Set("Content-Type", "application/json") + } +} + +func (c *client) getUIDFromGogs() { + req, err := http.NewRequest(http.MethodGet, c.apiURL+"user", http.NoBody) + utils.PanicOnError(err) + c.setHeadersOnRequest(req) + + response, err := c.httpClient.Do(req) + utils.PanicOnError(err) + + user := userResponse{} + utils.UnmarshalFromResponse(response, &user) + c.User = &user +} + +// NewClient creates new migrationClient +func NewClient(baseURL, pat string) *client { + client := &client{} + client.apiURL = strings.TrimRight(baseURL, "/") + "/api/v1/" + client.pat = pat + client.httpClient = &http.Client{} + client.getUIDFromGogs() + + return client +} diff --git a/utils/panicOnError.go b/utils/panicOnError.go new file mode 100644 index 0000000..7d61323 --- /dev/null +++ b/utils/panicOnError.go @@ -0,0 +1,8 @@ +package utils + +// PanicOnError will panic if err goven is not nil +func PanicOnError(err error) { + if err != nil { + panic(err) + } +} diff --git a/utils/readLine.go b/utils/readLine.go new file mode 100644 index 0000000..d540db7 --- /dev/null +++ b/utils/readLine.go @@ -0,0 +1,21 @@ +package utils + +import ( + "bufio" + "os" + "strings" +) + +var rd *bufio.Reader + +// ReadLine reads a line from stdin and strips newline from the end +func ReadLine() string { + if rd == nil { + rd = bufio.NewReader(os.Stdin) + } + + str, err := rd.ReadString('\n') + PanicOnError(err) + + return strings.TrimRight(str, "\r\n") +} diff --git a/utils/unmarshalFromResponse.go b/utils/unmarshalFromResponse.go new file mode 100644 index 0000000..e21827b --- /dev/null +++ b/utils/unmarshalFromResponse.go @@ -0,0 +1,16 @@ +package utils + +import ( + "encoding/json" + "io/ioutil" + "net/http" +) + +// UnmarshalFromResponse will unmarshal json from response body +func UnmarshalFromResponse(response *http.Response, v interface{}) { + responseBody, err := ioutil.ReadAll(response.Body) + PanicOnError(err) + + err = json.Unmarshal(responseBody, &v) + PanicOnError(err) +}