]> source.dussan.org Git - gitea.git/commitdiff
Add migration from GitBucket (#16767)
authorKN4CK3R <admin@oldschoolhack.me>
Sun, 14 Nov 2021 19:11:10 +0000 (20:11 +0100)
committerGitHub <noreply@github.com>
Sun, 14 Nov 2021 19:11:10 +0000 (20:11 +0100)
This PR adds [GitBucket](https://gitbucket.github.io/) as migration source.

Supported:
- Milestones
- Issues
- Pull Requests
- Comments
- Reviews
- Labels

There is no public usable instance so no integration tests added.

modules/convert/utils.go
modules/migrations/gitbucket.go [new file with mode: 0644]
modules/migrations/github.go
modules/structs/repo.go
options/locale/locale_en-US.ini
public/img/svg/gitea-gitbucket.svg [new file with mode: 0644]
templates/repo/migrate/gitbucket.tmpl [new file with mode: 0644]
web_src/svg/gitea-gitbucket.svg [new file with mode: 0644]

index a0463d7b10069daffbb85b4ea65b24bd788952a3..52fbcf547f72e05101f9f6dfd6d6f84d4e9aab89 100644 (file)
@@ -35,6 +35,8 @@ func ToGitServiceType(value string) structs.GitServiceType {
                return structs.GogsService
        case "onedev":
                return structs.OneDevService
+       case "gitbucket":
+               return structs.GitBucketService
        default:
                return structs.PlainGitService
        }
diff --git a/modules/migrations/gitbucket.go b/modules/migrations/gitbucket.go
new file mode 100644 (file)
index 0000000..72090c2
--- /dev/null
@@ -0,0 +1,72 @@
+// Copyright 2021 The Gitea 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 migrations
+
+import (
+       "context"
+       "net/url"
+       "strings"
+
+       "code.gitea.io/gitea/modules/migrations/base"
+       "code.gitea.io/gitea/modules/structs"
+)
+
+var (
+       _ base.Downloader        = &GitBucketDownloader{}
+       _ base.DownloaderFactory = &GitBucketDownloaderFactory{}
+)
+
+func init() {
+       RegisterDownloaderFactory(&GitBucketDownloaderFactory{})
+}
+
+// GitBucketDownloaderFactory defines a GitBucket downloader factory
+type GitBucketDownloaderFactory struct {
+}
+
+// New returns a Downloader related to this factory according MigrateOptions
+func (f *GitBucketDownloaderFactory) New(ctx context.Context, opts base.MigrateOptions) (base.Downloader, error) {
+       u, err := url.Parse(opts.CloneAddr)
+       if err != nil {
+               return nil, err
+       }
+
+       baseURL := u.Scheme + "://" + u.Host
+       fields := strings.Split(u.Path, "/")
+       oldOwner := fields[1]
+       oldName := strings.TrimSuffix(fields[2], ".git")
+
+       return NewGitBucketDownloader(ctx, baseURL, opts.AuthUsername, opts.AuthPassword, opts.AuthToken, oldOwner, oldName), nil
+}
+
+// GitServiceType returns the type of git service
+func (f *GitBucketDownloaderFactory) GitServiceType() structs.GitServiceType {
+       return structs.GitBucketService
+}
+
+// GitBucketDownloader implements a Downloader interface to get repository information
+// from GitBucket via GithubDownloader
+type GitBucketDownloader struct {
+       *GithubDownloaderV3
+}
+
+// NewGitBucketDownloader creates a GitBucket downloader
+func NewGitBucketDownloader(ctx context.Context, baseURL, userName, password, token, repoOwner, repoName string) *GitBucketDownloader {
+       githubDownloader := NewGithubDownloaderV3(ctx, baseURL, userName, password, token, repoOwner, repoName)
+       githubDownloader.SkipReactions = true
+       return &GitBucketDownloader{
+               githubDownloader,
+       }
+}
+
+// SupportGetRepoComments return true if it supports get repo comments
+func (g *GitBucketDownloader) SupportGetRepoComments() bool {
+       return false
+}
+
+// GetReviews is not supported
+func (g *GitBucketDownloader) GetReviews(context base.IssueContext) ([]*base.Review, error) {
+       return nil, &base.ErrNotSupported{Entity: "Reviews"}
+}
index 874cd054394f4db060a8c4aa2929a1854ae99d98..50cffc467a70f2387c72ce169b0d5b83bf86a2da 100644 (file)
@@ -68,15 +68,16 @@ func (f *GithubDownloaderV3Factory) GitServiceType() structs.GitServiceType {
 // from github via APIv3
 type GithubDownloaderV3 struct {
        base.NullDownloader
-       ctx          context.Context
-       clients      []*github.Client
-       repoOwner    string
-       repoName     string
-       userName     string
-       password     string
-       rates        []*github.Rate
-       curClientIdx int
-       maxPerPage   int
+       ctx           context.Context
+       clients       []*github.Client
+       repoOwner     string
+       repoName      string
+       userName      string
+       password      string
+       rates         []*github.Rate
+       curClientIdx  int
+       maxPerPage    int
+       SkipReactions bool
 }
 
 // NewGithubDownloaderV3 creates a github Downloader via github v3 API
@@ -428,25 +429,27 @@ func (g *GithubDownloaderV3) GetIssues(page, perPage int) ([]*base.Issue, bool,
 
                // get reactions
                var reactions []*base.Reaction
-               for i := 1; ; i++ {
-                       g.waitAndPickClient()
-                       res, resp, err := g.getClient().Reactions.ListIssueReactions(g.ctx, g.repoOwner, g.repoName, issue.GetNumber(), &github.ListOptions{
-                               Page:    i,
-                               PerPage: perPage,
-                       })
-                       if err != nil {
-                               return nil, false, err
-                       }
-                       g.setRate(&resp.Rate)
-                       if len(res) == 0 {
-                               break
-                       }
-                       for _, reaction := range res {
-                               reactions = append(reactions, &base.Reaction{
-                                       UserID:   reaction.User.GetID(),
-                                       UserName: reaction.User.GetLogin(),
-                                       Content:  reaction.GetContent(),
+               if !g.SkipReactions {
+                       for i := 1; ; i++ {
+                               g.waitAndPickClient()
+                               res, resp, err := g.getClient().Reactions.ListIssueReactions(g.ctx, g.repoOwner, g.repoName, issue.GetNumber(), &github.ListOptions{
+                                       Page:    i,
+                                       PerPage: perPage,
                                })
+                               if err != nil {
+                                       return nil, false, err
+                               }
+                               g.setRate(&resp.Rate)
+                               if len(res) == 0 {
+                                       break
+                               }
+                               for _, reaction := range res {
+                                       reactions = append(reactions, &base.Reaction{
+                                               UserID:   reaction.User.GetID(),
+                                               UserName: reaction.User.GetLogin(),
+                                               Content:  reaction.GetContent(),
+                                       })
+                               }
                        }
                }
 
@@ -516,25 +519,27 @@ func (g *GithubDownloaderV3) getComments(issueContext base.IssueContext) ([]*bas
                for _, comment := range comments {
                        // get reactions
                        var reactions []*base.Reaction
-                       for i := 1; ; i++ {
-                               g.waitAndPickClient()
-                               res, resp, err := g.getClient().Reactions.ListIssueCommentReactions(g.ctx, g.repoOwner, g.repoName, comment.GetID(), &github.ListOptions{
-                                       Page:    i,
-                                       PerPage: g.maxPerPage,
-                               })
-                               if err != nil {
-                                       return nil, err
-                               }
-                               g.setRate(&resp.Rate)
-                               if len(res) == 0 {
-                                       break
-                               }
-                               for _, reaction := range res {
-                                       reactions = append(reactions, &base.Reaction{
-                                               UserID:   reaction.User.GetID(),
-                                               UserName: reaction.User.GetLogin(),
-                                               Content:  reaction.GetContent(),
+                       if !g.SkipReactions {
+                               for i := 1; ; i++ {
+                                       g.waitAndPickClient()
+                                       res, resp, err := g.getClient().Reactions.ListIssueCommentReactions(g.ctx, g.repoOwner, g.repoName, comment.GetID(), &github.ListOptions{
+                                               Page:    i,
+                                               PerPage: g.maxPerPage,
                                        })
+                                       if err != nil {
+                                               return nil, err
+                                       }
+                                       g.setRate(&resp.Rate)
+                                       if len(res) == 0 {
+                                               break
+                                       }
+                                       for _, reaction := range res {
+                                               reactions = append(reactions, &base.Reaction{
+                                                       UserID:   reaction.User.GetID(),
+                                                       UserName: reaction.User.GetLogin(),
+                                                       Content:  reaction.GetContent(),
+                                               })
+                                       }
                                }
                        }
 
@@ -588,25 +593,27 @@ func (g *GithubDownloaderV3) GetAllComments(page, perPage int) ([]*base.Comment,
        for _, comment := range comments {
                // get reactions
                var reactions []*base.Reaction
-               for i := 1; ; i++ {
-                       g.waitAndPickClient()
-                       res, resp, err := g.getClient().Reactions.ListIssueCommentReactions(g.ctx, g.repoOwner, g.repoName, comment.GetID(), &github.ListOptions{
-                               Page:    i,
-                               PerPage: g.maxPerPage,
-                       })
-                       if err != nil {
-                               return nil, false, err
-                       }
-                       g.setRate(&resp.Rate)
-                       if len(res) == 0 {
-                               break
-                       }
-                       for _, reaction := range res {
-                               reactions = append(reactions, &base.Reaction{
-                                       UserID:   reaction.User.GetID(),
-                                       UserName: reaction.User.GetLogin(),
-                                       Content:  reaction.GetContent(),
+               if !g.SkipReactions {
+                       for i := 1; ; i++ {
+                               g.waitAndPickClient()
+                               res, resp, err := g.getClient().Reactions.ListIssueCommentReactions(g.ctx, g.repoOwner, g.repoName, comment.GetID(), &github.ListOptions{
+                                       Page:    i,
+                                       PerPage: g.maxPerPage,
                                })
+                               if err != nil {
+                                       return nil, false, err
+                               }
+                               g.setRate(&resp.Rate)
+                               if len(res) == 0 {
+                                       break
+                               }
+                               for _, reaction := range res {
+                                       reactions = append(reactions, &base.Reaction{
+                                               UserID:   reaction.User.GetID(),
+                                               UserName: reaction.User.GetLogin(),
+                                               Content:  reaction.GetContent(),
+                                       })
+                               }
                        }
                }
                idx := strings.LastIndex(*comment.IssueURL, "/")
@@ -656,25 +663,27 @@ func (g *GithubDownloaderV3) GetPullRequests(page, perPage int) ([]*base.PullReq
 
                // get reactions
                var reactions []*base.Reaction
-               for i := 1; ; i++ {
-                       g.waitAndPickClient()
-                       res, resp, err := g.getClient().Reactions.ListIssueReactions(g.ctx, g.repoOwner, g.repoName, pr.GetNumber(), &github.ListOptions{
-                               Page:    i,
-                               PerPage: perPage,
-                       })
-                       if err != nil {
-                               return nil, false, err
-                       }
-                       g.setRate(&resp.Rate)
-                       if len(res) == 0 {
-                               break
-                       }
-                       for _, reaction := range res {
-                               reactions = append(reactions, &base.Reaction{
-                                       UserID:   reaction.User.GetID(),
-                                       UserName: reaction.User.GetLogin(),
-                                       Content:  reaction.GetContent(),
+               if !g.SkipReactions {
+                       for i := 1; ; i++ {
+                               g.waitAndPickClient()
+                               res, resp, err := g.getClient().Reactions.ListIssueReactions(g.ctx, g.repoOwner, g.repoName, pr.GetNumber(), &github.ListOptions{
+                                       Page:    i,
+                                       PerPage: perPage,
                                })
+                               if err != nil {
+                                       return nil, false, err
+                               }
+                               g.setRate(&resp.Rate)
+                               if len(res) == 0 {
+                                       break
+                               }
+                               for _, reaction := range res {
+                                       reactions = append(reactions, &base.Reaction{
+                                               UserID:   reaction.User.GetID(),
+                                               UserName: reaction.User.GetLogin(),
+                                               Content:  reaction.GetContent(),
+                                       })
+                               }
                        }
                }
 
@@ -737,25 +746,27 @@ func (g *GithubDownloaderV3) convertGithubReviewComments(cs []*github.PullReques
        for _, c := range cs {
                // get reactions
                var reactions []*base.Reaction
-               for i := 1; ; i++ {
-                       g.waitAndPickClient()
-                       res, resp, err := g.getClient().Reactions.ListPullRequestCommentReactions(g.ctx, g.repoOwner, g.repoName, c.GetID(), &github.ListOptions{
-                               Page:    i,
-                               PerPage: g.maxPerPage,
-                       })
-                       if err != nil {
-                               return nil, err
-                       }
-                       g.setRate(&resp.Rate)
-                       if len(res) == 0 {
-                               break
-                       }
-                       for _, reaction := range res {
-                               reactions = append(reactions, &base.Reaction{
-                                       UserID:   reaction.User.GetID(),
-                                       UserName: reaction.User.GetLogin(),
-                                       Content:  reaction.GetContent(),
+               if !g.SkipReactions {
+                       for i := 1; ; i++ {
+                               g.waitAndPickClient()
+                               res, resp, err := g.getClient().Reactions.ListPullRequestCommentReactions(g.ctx, g.repoOwner, g.repoName, c.GetID(), &github.ListOptions{
+                                       Page:    i,
+                                       PerPage: g.maxPerPage,
                                })
+                               if err != nil {
+                                       return nil, err
+                               }
+                               g.setRate(&resp.Rate)
+                               if len(res) == 0 {
+                                       break
+                               }
+                               for _, reaction := range res {
+                                       reactions = append(reactions, &base.Reaction{
+                                               UserID:   reaction.User.GetID(),
+                                               UserName: reaction.User.GetLogin(),
+                                               Content:  reaction.GetContent(),
+                                       })
+                               }
                        }
                }
 
index 313a982f43d63f7829d2a41b82fd30ac5b074c57..8482a2128d7f520840ebb832fea8781e2d7af933 100644 (file)
@@ -242,13 +242,14 @@ type GitServiceType int
 
 // enumerate all GitServiceType
 const (
-       NotMigrated     GitServiceType = iota // 0 not migrated from external sites
-       PlainGitService                       // 1 plain git service
-       GithubService                         // 2 github.com
-       GiteaService                          // 3 gitea service
-       GitlabService                         // 4 gitlab service
-       GogsService                           // 5 gogs service
-       OneDevService                         // 6 onedev service
+       NotMigrated      GitServiceType = iota // 0 not migrated from external sites
+       PlainGitService                        // 1 plain git service
+       GithubService                          // 2 github.com
+       GiteaService                           // 3 gitea service
+       GitlabService                          // 4 gitlab service
+       GogsService                            // 5 gogs service
+       OneDevService                          // 6 onedev service
+       GitBucketService                       // 7 gitbucket service
 )
 
 // Name represents the service type's name
@@ -270,6 +271,8 @@ func (gt GitServiceType) Title() string {
                return "Gogs"
        case OneDevService:
                return "OneDev"
+       case GitBucketService:
+               return "GitBucket"
        case PlainGitService:
                return "Git"
        }
@@ -326,5 +329,6 @@ var (
                GiteaService,
                GogsService,
                OneDevService,
+               GitBucketService,
        }
 )
index 6fad20c87ed0085f67615cea182609feb0527f19..7d6c878ad6c287f6291cc723b1dc03cd8ac570f6 100644 (file)
@@ -911,6 +911,7 @@ migrate.gitlab.description = Migrate data from gitlab.com or other GitLab instan
 migrate.gitea.description = Migrate data from gitea.com or other Gitea instances.
 migrate.gogs.description = Migrate data from notabug.org or other Gogs instances.
 migrate.onedev.description = Migrate data from code.onedev.io or other OneDev instances.
+migrate.gitbucket.description = Migrating data from GitBucket instances.
 migrate.migrating_git = Migrating Git Data
 migrate.migrating_topics = Migrating Topics
 migrate.migrating_milestones = Migrating Milestones
diff --git a/public/img/svg/gitea-gitbucket.svg b/public/img/svg/gitea-gitbucket.svg
new file mode 100644 (file)
index 0000000..50ddd44
--- /dev/null
@@ -0,0 +1 @@
+<svg version="1.0" viewBox="0 0 316 329" class="svg gitea-gitbucket" width="16" height="16" aria-hidden="true"><g fill="#303030"><path d="M123 21.1c-44.8 2.8-84 12.8-97.1 24.6-5 4.5-5 7.1 0 11.6C41.7 71.5 96.6 82.9 150 83h10.6l5.4-5.6c11.8-12.1 21.3-12.4 32.6-1.2l5.2 5 13.3-1.7c33.8-4.2 61.5-12.7 71.8-22 5.3-4.8 5.3-7.2 0-12-10.1-9.1-39.1-18.1-70.4-21.9-28.3-3.4-65.6-4.4-95.5-2.5zM23.2 80.6c.4 1.6 7 42.9 14.8 91.9 7.9 49 14.7 89.5 15.2 90.2 1.7 2.1 25.8 11.4 41.6 15.9 13 3.7 35.1 8.4 40 8.4.6 0 1.2-.6 1.2-1.3 0-.6-17.4-18.5-38.6-39.7C57.9 206.6 55 203.2 55 196c0-7.2 3-10.7 38.3-45.9 30.1-30 34.8-34.3 36.6-33.5 1.1.5 8.7 7.4 17 15.2l15.1 14.4v5.9c0 7.3 2.4 12.4 7.7 16.7l3.8 3v61.8l-3.8 3.4c-10.2 8.9-10.2 22.9-.1 30.7 3.1 2.3 4.9 2.8 10.8 3.1 8.2.4 11.5-1.1 16.2-7.3 2.2-3 2.9-5.1 3.2-10 .4-6.5-.2-8.3-5.3-15.4l-2.5-3.4v-26.6c0-26.7.3-31.1 2.3-31.1.5 0 5.4 4.4 10.9 9.7 9.6 9.5 9.9 10 10.8 15.7 1.7 10.3 8.9 16.6 19 16.6 7.6 0 13.5-3.9 17.4-11.7 3.2-6.4 1.6-14.3-4.3-20.6-4.1-4.4-7.3-5.7-14.9-5.7h-6.8l-12.7-12.1c-10.7-10.1-12.7-12.6-13.2-15.7-1.2-7.2-1.6-8.2-4.7-11.7-3.9-4.5-7.7-6.5-12.2-6.5-1.9 0-4.5-.4-5.8-.9-1.3-.5-9.9-8.2-19.3-17l-17-16-7.1-.1c-10.6 0-36-2.7-52.4-5.5-22.8-4-38.5-8.6-57.9-17.2-1.1-.5-1.3 0-.9 2.3zM278.5 83.6c-8.6 3.6-28 8.8-42.5 11.4-6.9 1.2-12.9 2.6-13.5 3.1-.6.6 9.3 11.2 27.5 29.4 15.6 15.6 28.5 28.3 28.7 28.1.2-.2 1.9-15.8 3.8-34.7 1.9-18.9 3.7-35.6 4-37.2.6-3.4-.2-3.4-8-.1zM255.2 259.3c-7.8 7.8-14.2 14.6-14.2 15 0 .4.7.7 1.6.7 2.2 0 23-8.9 24.2-10.3.9-1.1 3.5-18.7 2.9-19.3-.2-.2-6.7 6.1-14.5 13.9zM56 283.5c0 3.4 4 9.5 8.4 12.9 6.1 4.6 19.7 10.4 31.7 13.5 16.9 4.3 32.1 6.2 53.4 6.8l19 .5-7-7.1c-6.8-6.9-7.1-7.1-12-7.1-18.9 0-55.1-7.9-80.6-17.6C62.5 283 57 281 56.6 281c-.3 0-.6 1.1-.6 2.5zM262 283.4c-5.3 2.8-25 9.7-36 12.6l-11.4 2.9-7.8 7.8c-4.2 4.2-7.6 7.9-7.4 8.1.9.8 24.4-4.1 33.4-6.9 16.4-5.3 26.7-11.4 30.8-18.5 2.4-4 3.1-7.4 1.7-7.4-.5.1-1.9.7-3.3 1.4z"/></g></svg>
\ No newline at end of file
diff --git a/templates/repo/migrate/gitbucket.tmpl b/templates/repo/migrate/gitbucket.tmpl
new file mode 100644 (file)
index 0000000..b554a3a
--- /dev/null
@@ -0,0 +1,129 @@
+{{template "base/head" .}}
+<div class="page-content repository new migrate">
+       <div class="ui middle very relaxed page grid">
+               <div class="column">
+                       <form class="ui form" action="{{.Link}}" method="post">
+                               {{.CsrfTokenHtml}}
+                               <h3 class="ui top attached header">
+                                       {{.i18n.Tr "repo.migrate.migrate" .service.Title}}
+                                       <input id="service_type" type="hidden" name="service" value="{{.service}}">
+                               </h3>
+                               <div class="ui attached segment">
+                                       {{template "base/alert" .}}
+                                       <div class="inline required field {{if .Err_CloneAddr}}error{{end}}">
+                                               <label for="clone_addr">{{.i18n.Tr "repo.migrate.clone_address"}}</label>
+                                               <input id="clone_addr" name="clone_addr" value="{{.clone_addr}}" autofocus required>
+                                               <span class="help">
+                                               {{.i18n.Tr "repo.migrate.clone_address_desc"}}{{if .ContextUser.CanImportLocal}} {{.i18n.Tr "repo.migrate.clone_local_path"}}{{end}}
+                                               </span>
+                                       </div>
+
+                                       <div class="inline field {{if .Err_Auth}}error{{end}}">
+                                               <label for="auth_username">{{.i18n.Tr "username"}}</label>
+                                               <input id="auth_username" name="auth_username" value="{{.auth_username}}" {{if not .auth_username}}data-need-clear="true"{{end}}>
+                                       </div>
+                                       <input class="fake" type="password">
+                                       <div class="inline field {{if .Err_Auth}}error{{end}}">
+                                               <label for="auth_password">{{.i18n.Tr "password"}}</label>
+                                               <input id="auth_password" name="auth_password" type="password" value="{{.auth_password}}">
+                                       </div>
+
+                                       {{template "repo/migrate/options" .}}
+
+                                       <span class="help">{{.i18n.Tr "repo.migrate.migrate_items_options"}}</span>
+                                       <div id="migrate_items">
+                                               <div class="inline field">
+                                                       <label>{{.i18n.Tr "repo.migrate_items"}}</label>
+                                                       <div class="ui checkbox">
+                                                               <input name="wiki" type="checkbox" {{if .wiki}}checked{{end}}>
+                                                               <label>{{.i18n.Tr "repo.migrate_items_wiki" | Safe}}</label>
+                                                       </div>
+                                                       <div class="ui checkbox">
+                                                               <input name="milestones" type="checkbox" {{if .milestones}}checked{{end}}>
+                                                               <label>{{.i18n.Tr "repo.migrate_items_milestones" | Safe}}</label>
+                                                       </div>
+                                               </div>
+                                               <div class="inline field">
+                                                       <label></label>
+                                                       <div class="ui checkbox">
+                                                               <input name="labels" type="checkbox" {{if .labels}}checked{{end}}>
+                                                               <label>{{.i18n.Tr "repo.migrate_items_labels" | Safe}}</label>
+                                                       </div>
+                                                       <div class="ui checkbox">
+                                                               <input name="issues" type="checkbox" {{if .issues}}checked{{end}}>
+                                                               <label>{{.i18n.Tr "repo.migrate_items_issues" | Safe}}</label>
+                                                       </div>
+                                               </div>
+                                               <div class="inline field">
+                                                       <label></label>
+                                                       <div class="ui checkbox">
+                                                               <input name="pull_requests" type="checkbox" {{if .pull_requests}}checked{{end}}>
+                                                               <label>{{.i18n.Tr "repo.migrate_items_pullrequests" | Safe}}</label>
+                                                       </div>
+                                                       <div class="ui checkbox">
+                                                               <input name="releases" type="checkbox" {{if .releases}}checked{{end}}>
+                                                               <label>{{.i18n.Tr "repo.migrate_items_releases" | Safe}}</label>
+                                                       </div>
+                                               </div>
+                                       </div>
+
+                                       <div class="ui divider"></div>
+
+                                       <div class="inline required field {{if .Err_Owner}}error{{end}}">
+                                               <label>{{.i18n.Tr "repo.owner"}}</label>
+                                               <div class="ui selection owner dropdown">
+                                                       <input type="hidden" id="uid" name="uid" value="{{.ContextUser.ID}}" required>
+                                                       <span class="text truncated-item-container" title="{{.ContextUser.Name}}">
+                                                               {{avatar .ContextUser 28 "mini"}}
+                                                               <span class="truncated-item-name">{{.ContextUser.ShortName 40}}</span>
+                                                       </span>
+                                                       {{svg "octicon-triangle-down" 14 "dropdown icon"}}
+                                                       <div class="menu" title="{{.SignedUser.Name}}">
+                                                               <div class="item truncated-item-container" data-value="{{.SignedUser.ID}}">
+                                                                       {{avatar .SignedUser 28 "mini"}}
+                                                                       <span class="truncated-item-name">{{.SignedUser.ShortName 40}}</span>
+                                                               </div>
+                                                               {{range .Orgs}}
+                                                                       <div class="item truncated-item-container" data-value="{{.ID}}" title="{{.Name}}">
+                                                                               {{avatar . 28 "mini"}}
+                                                                               <span class="truncated-item-name">{{.ShortName 40}}</span>
+                                                                       </div>
+                                                               {{end}}
+                                                       </div>
+                                               </div>
+                                       </div>
+
+                                       <div class="inline required field {{if .Err_RepoName}}error{{end}}">
+                                               <label for="repo_name">{{.i18n.Tr "repo.repo_name"}}</label>
+                                               <input id="repo_name" name="repo_name" value="{{.repo_name}}" required>
+                                       </div>
+                                       <div class="inline field">
+                                               <label>{{.i18n.Tr "repo.visibility"}}</label>
+                                               <div class="ui checkbox">
+                                                       {{if .IsForcedPrivate}}
+                                                               <input name="private" type="checkbox" checked readonly>
+                                                               <label>{{.i18n.Tr "repo.visibility_helper_forced" | Safe}}</label>
+                                                       {{else}}
+                                                               <input name="private" type="checkbox" {{if .private}}checked{{end}}>
+                                                               <label>{{.i18n.Tr "repo.visibility_helper" | Safe}}</label>
+                                                       {{end}}
+                                               </div>
+                                       </div>
+                                       <div class="inline field {{if .Err_Description}}error{{end}}">
+                                               <label for="description">{{.i18n.Tr "repo.repo_desc"}}</label>
+                                               <textarea id="description" name="description">{{.description}}</textarea>
+                                       </div>
+
+                                       <div class="inline field">
+                                               <label></label>
+                                               <button class="ui green button">
+                                                       {{.i18n.Tr "repo.migrate_repo"}}
+                                               </button>
+                                               <a class="ui button" href="{{AppSubUrl}}/">{{.i18n.Tr "cancel"}}</a>
+                                       </div>
+                               </div>
+                       </form>
+               </div>
+       </div>
+</div>
+{{template "base/footer" .}}
diff --git a/web_src/svg/gitea-gitbucket.svg b/web_src/svg/gitea-gitbucket.svg
new file mode 100644 (file)
index 0000000..24da154
--- /dev/null
@@ -0,0 +1,39 @@
+<?xml version="1.0" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
+ "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
+<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
+ width="316.000000pt" height="329.000000pt" viewBox="0 0 316.000000 329.000000"
+ preserveAspectRatio="xMidYMid meet">
+<metadata>
+Created by potrace 1.14, written by Peter Selinger 2001-2017
+</metadata>
+<g transform="translate(0.000000,329.000000) scale(0.100000,-0.100000)"
+fill="#303030" stroke="none">
+<path d="M1230 3079 c-448 -28 -840 -128 -971 -246 -50 -45 -50 -71 0 -116
+158 -142 707 -256 1241 -257 l106 0 54 56 c118 121 213 124 326 12 l52 -50
+133 17 c338 42 615 127 718 220 53 48 53 72 0 120 -101 91 -391 181 -704 219
+-283 34 -656 44 -955 25z"/>
+<path d="M232 2484 c4 -16 70 -429 148 -919 79 -490 147 -895 152 -902 17 -21
+258 -114 416 -159 130 -37 351 -84 400 -84 6 0 12 6 12 13 0 6 -174 185 -386
+397 -395 394 -424 428 -424 500 0 72 30 107 383 459 301 300 348 343 366 335
+11 -5 87 -74 170 -152 l151 -144 0 -59 c0 -73 24 -124 77 -167 l38 -30 0 -309
+0 -309 -38 -34 c-102 -89 -102 -229 -1 -307 31 -23 49 -28 108 -31 82 -4 115
+11 162 73 22 30 29 51 32 100 4 65 -2 83 -53 154 l-25 34 0 266 c0 267 3 311
+23 311 5 0 54 -44 109 -97 96 -95 99 -100 108 -157 17 -103 89 -166 190 -166
+76 0 135 39 174 117 32 64 16 143 -43 206 -41 44 -73 57 -149 57 l-68 0 -127
+121 c-107 101 -127 126 -132 157 -12 72 -16 82 -47 117 -39 45 -77 65 -122 65
+-19 0 -45 4 -58 9 -13 5 -99 82 -193 170 l-170 160 -71 1 c-106 0 -360 27
+-524 55 -228 40 -385 86 -579 172 -11 5 -13 0 -9 -23z"/>
+<path d="M2785 2454 c-86 -36 -280 -88 -425 -114 -69 -12 -129 -26 -135 -31
+-6 -6 93 -112 275 -294 156 -156 285 -283 287 -281 2 2 19 158 38 347 19 189
+37 356 40 372 6 34 -2 34 -80 1z"/>
+<path d="M2552 697 c-78 -78 -142 -146 -142 -150 0 -4 7 -7 16 -7 22 0 230 89
+242 103 9 11 35 187 29 193 -2 2 -67 -61 -145 -139z"/>
+<path d="M560 455 c0 -34 40 -95 84 -129 61 -46 197 -104 317 -135 169 -43
+321 -62 534 -68 l190 -5 -70 71 c-68 69 -71 71 -120 71 -189 0 -551 79 -806
+176 -64 24 -119 44 -123 44 -3 0 -6 -11 -6 -25z"/>
+<path d="M2620 456 c-53 -28 -250 -97 -360 -126 l-114 -29 -78 -78 c-42 -42
+-76 -79 -74 -81 9 -8 244 41 334 69 164 53 267 114 308 185 24 40 31 74 17 74
+-5 -1 -19 -7 -33 -14z"/>
+</g>
+</svg>
\ No newline at end of file