diff --git a/cmd/import.go b/cmd/import.go index 31506a6ef..220a58675 100644 --- a/cmd/import.go +++ b/cmd/import.go @@ -30,6 +30,15 @@ This can be used to migrate from other service to Gogs.`, stringFlag("token", "", "private authentication token"), }, }, + { + Name: "redmine", + Usage: "import from Redmine", + Action: importGitLab, + Flags: []cli.Flag{ + stringFlag("url", "", "Redmine service base URL"), + stringFlag("token", "", "private authentication token"), + }, + }, }, Flags: []cli.Flag{ stringFlag("config, c", "custom/conf/app.ini", "Custom configuration file path"), @@ -63,3 +72,30 @@ func importGitLab(ctx *cli.Context) { log.Println("Import failed!") } } + +func importRedmine(ctx *cli.Context) { + baseUrl, err := url.Parse(ctx.String("url")) + if err != nil { + log.Fatal("Required --url parameter is missing or not valid URL") + } + if len(baseUrl.Path) > 0 { + log.Fatal("Provided --url parameter must not contain path") + } + token := ctx.String("token") + if len(token) == 0 { + log.Fatal("Missing required --token parameter") + } + + if ctx.IsSet("config") { + setting.CustomConf = ctx.String("config") + } + setting.NewContext() + models.LoadConfigs() + models.SetEngine() + + if importer.ImportRedmine(baseUrl, token) == nil { + log.Println("Finished importing!") + } else { + log.Println("Import failed!") + } +} diff --git a/modules/importer/redmine.go b/modules/importer/redmine.go new file mode 100644 index 000000000..1834e323a --- /dev/null +++ b/modules/importer/redmine.go @@ -0,0 +1,72 @@ +// Copyright 2015 The Gogs Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package importer + +import ( + "fmt" + "log" + "net/url" + "sort" + "time" + + "github.com/gogits/gogs/models" +) + +func ImportRedmine(baseUrl *url.URL, token string) error { + if err := importRedmineUsers(baseUrl, token); err != nil { + return err + } + return nil +} + +// http://www.redmine.org/projects/redmine/wiki/Rest_api +// http://www.redmine.org/projects/redmine/wiki/Rest_Users + +type RedmineUser struct { + Id int64 `json:"id"` + Name string `json:"login"` + FirstName string `json:"firstname"` + LastName string `json:"lastname"` + Email string `json:"mail"` + Passwd string `json:"password_hash"` // this requires API patch (returns bcrypt hash) + Created time.Time `json:"created_on"` +} +type RedmineUsers []RedmineUser + +func (slice RedmineUsers) Len() int { return len(slice) } +func (slice RedmineUsers) Less(i, j int) bool { return slice[i].Id < slice[j].Id } +func (slice RedmineUsers) Swap(i, j int) { slice[i], slice[j] = slice[j], slice[i] } + +func importRedmineUsers(baseUrl *url.URL, token string) error { + var remoteUsers RedmineUsers + if err := fetchObjects("/users.json", baseUrl, token, &remoteUsers); err != nil { + return err + } + sort.Sort(remoteUsers) + + for _, remoteUser := range remoteUsers { + if user, _ := models.GetUserByName(remoteUser.Name); user == nil { + fullName := fmt.Sprintf("%s %s", remoteUser.FirstName, remoteUser.LastName) + log.Printf("User: %s (%s)", remoteUser.Name, fullName) + user := &models.User{ + Name: remoteUser.Name, + FullName: fullName, + Email: remoteUser.Email, + IsActive: true, + IsAdmin: true, + Created: remoteUser.Created, + LoginType: models.PLAIN, + } + err := models.CreateUser(user) + if err != nil { + log.Fatalf("Cannot create user: %s", err) + return err + } + } else { + log.Printf("User %s already exists!", remoteUser.Name) + } + } + return nil +}