You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
349 lines
9.9 KiB
Go
349 lines
9.9 KiB
Go
package main
|
|
|
|
import (
|
|
"github.com/xanzy/go-gitlab"
|
|
// "github.com/gogits/go-gogs-client"
|
|
"github.com/mruschmann/go-gogs-client"
|
|
"fmt"
|
|
"bufio"
|
|
"os"
|
|
"strconv"
|
|
"strings"
|
|
"encoding/json"
|
|
"io/ioutil"
|
|
)
|
|
|
|
|
|
/** The configuration that will be loaded from a JSON file */
|
|
type Configuration struct {
|
|
GitlabURL string; ///< URL to the Gitlab API interface
|
|
GitlabAPIKey string; ///< API Key for Gitlab
|
|
GogsURL string; ///< URL to the Gogs API interface
|
|
GogsAPIKey string; ///< API Key for Gogs
|
|
UserMap []UsersMap; ///< Map of Gitlab usernames to Gogs usernames
|
|
}
|
|
|
|
|
|
/** An instance of the Configuration to store the loaded data */
|
|
var config Configuration
|
|
|
|
|
|
/** The main routine for this program, which migrates a Gitlab project to Gogs
|
|
*
|
|
* 1. Reads the configuration from config.json.
|
|
* 2. Polls the Gitlab server for projects
|
|
* 3. Prompts the user for the Gitlab project to migrate
|
|
* 4. Pools the Gogs server for projects
|
|
* 5. Prompts the user for the Gogs project to migrate
|
|
* 6. Simulates the migration without writing to the Gogs API
|
|
* 7. Prompts the user to press <Enter> to perform the migration
|
|
* 8. Performs the migration of Gitlab project to Gogs Project
|
|
*/
|
|
func main() {
|
|
var projPtr []*gogs.Repository
|
|
reader := bufio.NewReader(os.Stdin)
|
|
found := false
|
|
num := 0
|
|
var gogsPrj *gogs.Repository
|
|
var gitPrj *gitlab.Project
|
|
|
|
// Load configuration from config.json
|
|
file, err9 := ioutil.ReadFile("./config.json")
|
|
CheckError(err9)
|
|
err9 = json.Unmarshal(file, &config)
|
|
fmt.Println("GitlabURL:", config.GitlabURL)
|
|
fmt.Println("GitlabAPIKey:", config.GitlabAPIKey)
|
|
fmt.Println("GogsURL:", config.GogsURL)
|
|
fmt.Println("GogsAPIKey:", config.GogsAPIKey)
|
|
fmt.Println("UserMap: [")
|
|
for i := range config.UserMap {
|
|
fmt.Println("\t", config.UserMap[i].From, "to", config.UserMap[i].To)
|
|
}
|
|
fmt.Println("]")
|
|
CheckError(err9)
|
|
|
|
// Have user select a source project from gitlab
|
|
git := gitlab.NewClient(nil, config.GitlabAPIKey)
|
|
git.SetBaseURL(config.GitlabURL)
|
|
opt := &gitlab.ListProjectsOptions{}
|
|
gitlabProjects, _, err := git.Projects.ListProjects(opt)
|
|
CheckError(err)
|
|
|
|
fmt.Println("")
|
|
for i := range gitlabProjects {
|
|
fmt.Println(gitlabProjects[i].ID, ":", gitlabProjects[i].Name)
|
|
}
|
|
|
|
fmt.Printf("Select source gitlab project: ")
|
|
text, _ := reader.ReadString('\n')
|
|
text = strings.Trim(text, "\n")
|
|
|
|
for i := range gitlabProjects {
|
|
num, _ = strconv.Atoi(text)
|
|
if num == gitlabProjects[i].ID {
|
|
found = true
|
|
gitPrj = gitlabProjects[i]
|
|
} // else purposefully omitted
|
|
}
|
|
if !found {
|
|
fmt.Println(text, "not found")
|
|
os.Exit(1)
|
|
} // else purposefully omitted
|
|
|
|
// Have user select a destination project in gogs
|
|
gg := gogs.NewClient(config.GogsURL, config.GogsAPIKey)
|
|
projPtr, err = gg.ListMyRepos()
|
|
CheckError(err)
|
|
|
|
fmt.Println("")
|
|
for i := range projPtr {
|
|
fmt.Println(projPtr[i].ID, ":", projPtr[i].Name)
|
|
}
|
|
|
|
fmt.Printf("Select destination gogs project: ")
|
|
text, _ = reader.ReadString('\n')
|
|
text = strings.Trim(text, "\n")
|
|
|
|
for i := range projPtr {
|
|
num, _ = strconv.Atoi(text)
|
|
if int64(num) == projPtr[i].ID {
|
|
found = true
|
|
gogsPrj = projPtr[i]
|
|
} // else purposefully omitted
|
|
}
|
|
if !found {
|
|
fmt.Println(text, "not found")
|
|
os.Exit(1)
|
|
} // else purposefully omitted
|
|
|
|
// Perform pre merge
|
|
fmt.Println("\nSimulated migration of", gitPrj.Name, "to", gogsPrj.Name)
|
|
DoMigration(true, git, gg, gitPrj.ID, gogsPrj.Name, gogsPrj.Owner.UserName)
|
|
|
|
// Perform actual migration
|
|
fmt.Println("\nCompleted simulation. Press <Enter> to perform migration...")
|
|
text, _ = reader.ReadString('\n')
|
|
DoMigration(false, git, gg, gitPrj.ID, gogsPrj.Name, gogsPrj.Owner.UserName)
|
|
|
|
os.Exit(0)
|
|
}
|
|
|
|
|
|
/** A map of a milestone from its Gitlab ID to its new Gogs ID */
|
|
type MilestoneMap struct {
|
|
from int ///< ID in Gitlab
|
|
to int64 ///< New ID in Gogs
|
|
}
|
|
|
|
|
|
/** Performs a migration
|
|
* \param dryrun Does not write to the Gogs API if true
|
|
* \param git A gitlab client for making API calls
|
|
* \param gg A gogs client for making API calls
|
|
* \param gitPrj ID of the Gitlab project to migrate from
|
|
* \param gogsPrj The name of the Gitlab project to migrate into
|
|
* \param owner The owner of gogsPrj, which is required to make API calls
|
|
*
|
|
* This function migrates the Milestones first. It creates a map from the old
|
|
* Gitlab milestone IDs to the new Gogs milestone IDs. It uses these IDs to
|
|
* migrate the issues. For each issue, it migrates all of the comments.
|
|
*/
|
|
func DoMigration(dryrun bool, git *gitlab.Client, gg *gogs.Client, gitPrj int, gogsPrj string, owner string) {
|
|
var mmap []MilestoneMap
|
|
var listMiles gitlab.ListMilestonesOptions
|
|
var listIssues gitlab.ListProjectIssuesOptions
|
|
var listNotes gitlab.ListIssueNotesOptions
|
|
var err error
|
|
var milestone *gogs.Milestone
|
|
var issueIndex int64
|
|
issueNum := 0
|
|
sort := "asc"
|
|
|
|
listIssues.PerPage = 1000
|
|
listIssues.Sort = &sort
|
|
|
|
// Migrate all of the milestones
|
|
milestones, _, err0 := git.Milestones.ListMilestones(gitPrj, &listMiles)
|
|
CheckError(err0)
|
|
for i := range milestones {
|
|
fmt.Println("Create Milestone:", milestones[i].Title)
|
|
|
|
var opt gogs.CreateMilestoneOption
|
|
opt.Title = milestones[i].Title
|
|
opt.Description = milestones[i].Description
|
|
if !dryrun {
|
|
// Never write to the API during a dryrun
|
|
milestone, err = gg.CreateMilestone(owner, gogsPrj, opt)
|
|
CheckError(err)
|
|
mmap = append(mmap, MilestoneMap{milestones[i].ID, milestone.ID})
|
|
} // else purposefully omitted
|
|
|
|
if milestones[i].State == "closed" {
|
|
fmt.Println("Marking as closed")
|
|
var opt2 gogs.EditMilestoneOption
|
|
opt2.Title = opt.Title
|
|
opt2.Description = &opt.Description
|
|
opt2.State = &milestones[i].State
|
|
if !dryrun {
|
|
// Never write to the API during a dryrun
|
|
milestone, err = gg.EditMilestone(owner, gogsPrj, milestone.ID, opt2)
|
|
CheckError(err)
|
|
} // else purposefully omitted
|
|
} // else purposefully omitted
|
|
}
|
|
|
|
// Migrate all of the issues
|
|
issues, _, err1 := git.Issues.ListProjectIssues(gitPrj, &listIssues)
|
|
CheckError(err1)
|
|
for i := range issues {
|
|
issueNum++
|
|
if issueNum == issues[i].IID {
|
|
fmt.Println("Create Issue", issues[i].IID, ":", issues[i].Title)
|
|
|
|
var opt gogs.CreateIssueOption
|
|
opt.Title = issues[i].Title
|
|
opt.Body = issues[i].Description
|
|
opt.Assignee = MapUser(issues[i].Author.Username) // Gitlab user to Gogs user map
|
|
if (issues[i].Milestone != nil) {
|
|
opt.Milestone = MapMilestone(mmap, issues[i].Milestone.ID)
|
|
}
|
|
opt.Closed = issues[i].State == "closed"
|
|
if !dryrun {
|
|
// Never write to the API during a dryrun
|
|
for k := range issues[i].Labels {
|
|
opt.Labels = append(opt.Labels, GetIssueLabel(git, gg, gitPrj, gogsPrj, owner, issues[i].Labels[k]));
|
|
}
|
|
issue, err6 := gg.CreateIssue(owner, gogsPrj, opt)
|
|
issueIndex = issue.Index
|
|
CheckError(err6)
|
|
} // else purposefully omitted
|
|
|
|
// Migrate all of the issue notes
|
|
notes, _, err2 := git.Notes.ListIssueNotes(gitPrj, issues[i].ID, &listNotes)
|
|
CheckError(err2)
|
|
for j := range notes {
|
|
fmt.Println("Adding note", notes[j].ID)
|
|
|
|
var opt2 gogs.CreateIssueCommentOption
|
|
//var opt3 gogs.EditIssueCommentOption
|
|
opt2.Body = notes[j].Body
|
|
//opt3.Body = notes[j].Body
|
|
if !dryrun {
|
|
// Never write to the API during a dryrun
|
|
_, err := gg.CreateIssueComment(owner, gogsPrj, issueIndex, opt2)
|
|
//_, err = gg.EditIssueComment(owner, gogsPrj, issueIndex, comment.ID, opt3)
|
|
CheckError(err)
|
|
} // else purposefully omitted
|
|
}
|
|
} else {
|
|
// TODO Create a temp issue and delete it later (MCR 9/29/16)
|
|
fmt.Println("Issues not in order!!")
|
|
fmt.Println("Preservation of skipped issues IDs is not implemented")
|
|
os.Exit(1)
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/** Find the ID of an label from its name or create a new label
|
|
* \param git A gitlab client for making API calls
|
|
* \param gg A gogs client for making API calls
|
|
* \param gitPrj ID of the Gitlab project to migrate from
|
|
* \param gogsPrj The name of the Gitlab project to migrate into
|
|
* \param owner The owner of gogsPrj, which is required to make API calls
|
|
* \param label The name of the label to find or create
|
|
* \return The ID of the tag in Gogs
|
|
*/
|
|
func GetIssueLabel(git *gitlab.Client, gg *gogs.Client, gitPrj int, gogsPrj string, owner string, label string) (int64) {
|
|
ID := int64(-1)
|
|
found := false
|
|
|
|
labels, err := gg.ListRepoLabels(owner, gogsPrj)
|
|
CheckError(err)
|
|
for i := range labels {
|
|
if labels[i].Name == label {
|
|
fmt.Println("Found label", label)
|
|
ID = labels[i].ID
|
|
found = true
|
|
}
|
|
}
|
|
|
|
if !found {
|
|
tags, _, err1 := git.Labels.ListLabels(gitPrj)
|
|
CheckError(err1)
|
|
for i:= range tags {
|
|
if tags[i].Name == label {
|
|
fmt.Println("Create label", label, "color", tags[i].Color)
|
|
var opt gogs.CreateLabelOption
|
|
opt.Name = label
|
|
opt.Color = tags[i].Color
|
|
tag, err2 := gg.CreateLabel(owner, gogsPrj, opt)
|
|
CheckError(err2)
|
|
found = true
|
|
ID = tag.ID
|
|
}
|
|
}
|
|
} // else purposefully omitted
|
|
|
|
if !found {
|
|
fmt.Println("Unable to find label", label, "in gitlab!!")
|
|
os.Exit(5)
|
|
} // else purposefully omitted
|
|
|
|
return ID
|
|
}
|
|
|
|
|
|
/** An entry in the user map from Gitlab to Gogs */
|
|
type UsersMap struct {
|
|
From string ///< The user name to map from
|
|
To string ///< The user name to map to
|
|
}
|
|
|
|
|
|
/** Maps a Gitlab user name to the desired Gogs user name
|
|
* @param user The Gitlab user name to map
|
|
* @return The Gogs user name
|
|
*/
|
|
func MapUser(user string) (string) {
|
|
u := user
|
|
|
|
for i := range config.UserMap {
|
|
if user == config.UserMap[i].From {
|
|
u = config.UserMap[i].To
|
|
} // else purposefully omitted
|
|
}
|
|
|
|
return u
|
|
}
|
|
|
|
|
|
/** Maps a Gitlab milestone to the desired Gogs milestone
|
|
* @param mmap An array of ID maps from Gitlab to Gogs
|
|
* @param user The Gitlab milestone to map
|
|
* @return The Gogs milstone
|
|
*/
|
|
func MapMilestone(mmap []MilestoneMap, ID int) (int64) {
|
|
var toID int64
|
|
toID = int64(ID)
|
|
|
|
for i := range mmap {
|
|
if (mmap[i].from == ID) {
|
|
toID = mmap[i].to
|
|
} // else purposefully omitted
|
|
}
|
|
|
|
return toID
|
|
}
|
|
|
|
|
|
/** Checks an error code and exists if not nil
|
|
* @param err The error code to check
|
|
*/
|
|
func CheckError(err error) {
|
|
if err != nil {
|
|
fmt.Println(err)
|
|
os.Exit(1)
|
|
} // else purposefully omitted
|
|
}
|