69 changes: 43 additions & 26 deletions modules/migrations/gitlab.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,17 +63,14 @@ func (f *GitlabDownloaderFactory) GitServiceType() structs.GitServiceType {
// from gitlab via go-gitlab
// - issueCount is incremented in GetIssues() to ensure PR and Issue numbers do not overlap,
// because Gitlab has individual Issue and Pull Request numbers.
// - issueSeen, working alongside issueCount, is checked in GetComments() to see whether we
// need to fetch the Issue or PR comments, as Gitlab stores them separately.
type GitlabDownloader struct {
base.NullDownloader
ctx context.Context
client *gitlab.Client
repoID int
repoName string
issueCount int64
fetchPRcomments bool
maxPerPage int
ctx context.Context
client *gitlab.Client
repoID int
repoName string
issueCount int64
maxPerPage int
}

// NewGitlabDownloader creates a gitlab Downloader via gitlab API
Expand Down Expand Up @@ -364,6 +361,20 @@ func (g *GitlabDownloader) GetReleases() ([]*base.Release, error) {
return releases, nil
}

type gitlabIssueContext struct {
foreignID int64
localID int64
IsMergeRequest bool
}

func (c gitlabIssueContext) LocalID() int64 {
return c.localID
}

func (c gitlabIssueContext) ForeignID() int64 {
return c.foreignID
}

// GetIssues returns issues according start and limit
// Note: issue label description and colors are not supported by the go-gitlab library at this time
func (g *GitlabDownloader) GetIssues(page, perPage int) ([]*base.Issue, bool, error) {
Expand Down Expand Up @@ -433,6 +444,11 @@ func (g *GitlabDownloader) GetIssues(page, perPage int) ([]*base.Issue, bool, er
Closed: issue.ClosedAt,
IsLocked: issue.DiscussionLocked,
Updated: *issue.UpdatedAt,
Context: gitlabIssueContext{
foreignID: int64(issue.IID),
localID: int64(issue.IID),
IsMergeRequest: false,
},
})

// increment issueCount, to be used in GetPullRequests()
Expand All @@ -445,27 +461,26 @@ func (g *GitlabDownloader) GetIssues(page, perPage int) ([]*base.Issue, bool, er
// GetComments returns comments according issueNumber
// TODO: figure out how to transfer comment reactions
func (g *GitlabDownloader) GetComments(opts base.GetCommentOptions) ([]*base.Comment, bool, error) {
var issueNumber = opts.IssueNumber
context, ok := opts.Context.(gitlabIssueContext)
if !ok {
return nil, false, fmt.Errorf("unexpected context: %+v", opts.Context)
}

var allComments = make([]*base.Comment, 0, g.maxPerPage)

var page = 1
var realIssueNumber int64

for {
var comments []*gitlab.Discussion
var resp *gitlab.Response
var err error
// fetchPRcomments decides whether to fetch Issue or PR comments
if !g.fetchPRcomments {
realIssueNumber = issueNumber
comments, resp, err = g.client.Discussions.ListIssueDiscussions(g.repoID, int(realIssueNumber), &gitlab.ListIssueDiscussionsOptions{
if !context.IsMergeRequest {
comments, resp, err = g.client.Discussions.ListIssueDiscussions(g.repoID, int(context.ForeignID()), &gitlab.ListIssueDiscussionsOptions{
Page: page,
PerPage: g.maxPerPage,
}, nil, gitlab.WithContext(g.ctx))
} else {
// If this is a PR, we need to figure out the Gitlab/original PR ID to be passed below
realIssueNumber = issueNumber - g.issueCount
comments, resp, err = g.client.Discussions.ListMergeRequestDiscussions(g.repoID, int(realIssueNumber), &gitlab.ListMergeRequestDiscussionsOptions{
comments, resp, err = g.client.Discussions.ListMergeRequestDiscussions(g.repoID, int(context.ForeignID()), &gitlab.ListMergeRequestDiscussionsOptions{
Page: page,
PerPage: g.maxPerPage,
}, nil, gitlab.WithContext(g.ctx))
Expand All @@ -479,7 +494,7 @@ func (g *GitlabDownloader) GetComments(opts base.GetCommentOptions) ([]*base.Com
if !comment.IndividualNote {
for _, note := range comment.Notes {
allComments = append(allComments, &base.Comment{
IssueIndex: realIssueNumber,
IssueIndex: context.LocalID(),
PosterID: int64(note.Author.ID),
PosterName: note.Author.Username,
PosterEmail: note.Author.Email,
Expand All @@ -490,7 +505,7 @@ func (g *GitlabDownloader) GetComments(opts base.GetCommentOptions) ([]*base.Com
} else {
c := comment.Notes[0]
allComments = append(allComments, &base.Comment{
IssueIndex: realIssueNumber,
IssueIndex: context.LocalID(),
PosterID: int64(c.Author.ID),
PosterName: c.Author.Username,
PosterEmail: c.Author.Email,
Expand Down Expand Up @@ -521,9 +536,6 @@ func (g *GitlabDownloader) GetPullRequests(page, perPage int) ([]*base.PullReque
},
}

// Set fetchPRcomments to true here, so PR comments are fetched instead of Issue comments
g.fetchPRcomments = true

var allPRs = make([]*base.PullRequest, 0, perPage)

prs, _, err := g.client.MergeRequests.ListProjectMergeRequests(g.repoID, opt, nil, gitlab.WithContext(g.ctx))
Expand Down Expand Up @@ -587,7 +599,6 @@ func (g *GitlabDownloader) GetPullRequests(page, perPage int) ([]*base.PullReque
allPRs = append(allPRs, &base.PullRequest{
Title: pr.Title,
Number: newPRNumber,
OriginalNumber: int64(pr.IID),
PosterName: pr.Author.Username,
PosterID: int64(pr.Author.ID),
Content: pr.Description,
Expand Down Expand Up @@ -615,15 +626,20 @@ func (g *GitlabDownloader) GetPullRequests(page, perPage int) ([]*base.PullReque
OwnerName: pr.Author.Username,
},
PatchURL: pr.WebURL + ".patch",
Context: gitlabIssueContext{
foreignID: int64(pr.IID),
localID: newPRNumber,
IsMergeRequest: true,
},
})
}

return allPRs, len(prs) < perPage, nil
}

// GetReviews returns pull requests review
func (g *GitlabDownloader) GetReviews(pullRequestNumber int64) ([]*base.Review, error) {
approvals, resp, err := g.client.MergeRequestApprovals.GetConfiguration(g.repoID, int(pullRequestNumber), gitlab.WithContext(g.ctx))
func (g *GitlabDownloader) GetReviews(context base.IssueContext) ([]*base.Review, error) {
approvals, resp, err := g.client.MergeRequestApprovals.GetConfiguration(g.repoID, int(context.ForeignID()), gitlab.WithContext(g.ctx))
if err != nil {
if resp != nil && resp.StatusCode == 404 {
log.Error(fmt.Sprintf("GitlabDownloader: while migrating a error occurred: '%s'", err.Error()))
Expand All @@ -635,6 +651,7 @@ func (g *GitlabDownloader) GetReviews(pullRequestNumber int64) ([]*base.Review,
var reviews = make([]*base.Review, 0, len(approvals.ApprovedBy))
for _, user := range approvals.ApprovedBy {
reviews = append(reviews, &base.Review{
IssueIndex: context.LocalID(),
ReviewerID: int64(user.User.ID),
ReviewerName: user.User.Username,
CreatedAt: *approvals.UpdatedAt,
Expand Down
32 changes: 20 additions & 12 deletions modules/migrations/gitlab_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,11 @@ func TestGitlabDownloadRepo(t *testing.T) {
}, issues)

comments, _, err := downloader.GetComments(base.GetCommentOptions{
IssueNumber: 2,
Context: gitlabIssueContext{
foreignID: 2,
localID: 2,
IsMergeRequest: false,
},
})
assert.NoError(t, err)
assertCommentsEqual(t, []*base.Comment{
Expand Down Expand Up @@ -252,15 +256,14 @@ func TestGitlabDownloadRepo(t *testing.T) {
assert.NoError(t, err)
assertPullRequestsEqual(t, []*base.PullRequest{
{
Number: 4,
OriginalNumber: 2,
Title: "Test branch",
Content: "do not merge this PR",
Milestone: "1.0.0",
PosterID: 1241334,
PosterName: "lafriks",
State: "opened",
Created: time.Date(2019, 11, 28, 15, 56, 54, 104000000, time.UTC),
Number: 4,
Title: "Test branch",
Content: "do not merge this PR",
Milestone: "1.0.0",
PosterID: 1241334,
PosterName: "lafriks",
State: "opened",
Created: time.Date(2019, 11, 28, 15, 56, 54, 104000000, time.UTC),
Labels: []*base.Label{
{
Name: "bug",
Expand Down Expand Up @@ -293,10 +296,15 @@ func TestGitlabDownloadRepo(t *testing.T) {
Merged: false,
MergedTime: nil,
MergeCommitSHA: "",
Context: gitlabIssueContext{
foreignID: 2,
localID: 4,
IsMergeRequest: true,
},
},
}, prs)

rvs, err := downloader.GetReviews(1)
rvs, err := downloader.GetReviews(base.BasicIssueContext(1))
assert.NoError(t, err)
assertReviewsEqual(t, []*base.Review{
{
Expand All @@ -313,7 +321,7 @@ func TestGitlabDownloadRepo(t *testing.T) {
},
}, rvs)

rvs, err = downloader.GetReviews(2)
rvs, err = downloader.GetReviews(base.BasicIssueContext(2))
assert.NoError(t, err)
assertReviewsEqual(t, []*base.Review{
{
Expand Down
6 changes: 3 additions & 3 deletions modules/migrations/gogs.go
Original file line number Diff line number Diff line change
Expand Up @@ -228,10 +228,9 @@ func (g *GogsDownloader) getIssues(page int, state string) ([]*base.Issue, bool,

// GetComments returns comments according issueNumber
func (g *GogsDownloader) GetComments(opts base.GetCommentOptions) ([]*base.Comment, bool, error) {
var issueNumber = opts.IssueNumber
var allComments = make([]*base.Comment, 0, 100)

comments, err := g.client.ListIssueComments(g.repoOwner, g.repoName, issueNumber)
comments, err := g.client.ListIssueComments(g.repoOwner, g.repoName, opts.Context.ForeignID())
if err != nil {
return nil, false, fmt.Errorf("error while listing repos: %v", err)
}
Expand All @@ -240,7 +239,7 @@ func (g *GogsDownloader) GetComments(opts base.GetCommentOptions) ([]*base.Comme
continue
}
allComments = append(allComments, &base.Comment{
IssueIndex: issueNumber,
IssueIndex: opts.Context.LocalID(),
PosterID: comment.Poster.ID,
PosterName: comment.Poster.Login,
PosterEmail: comment.Poster.Email,
Expand Down Expand Up @@ -304,6 +303,7 @@ func convertGogsIssue(issue *gogs.Issue) *base.Issue {
Updated: issue.Updated,
Labels: labels,
Closed: closed,
Context: base.BasicIssueContext(issue.Index),
}
}

Expand Down
2 changes: 1 addition & 1 deletion modules/migrations/gogs_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ func TestGogsDownloadRepo(t *testing.T) {

// downloader.GetComments()
comments, _, err := downloader.GetComments(base.GetCommentOptions{
IssueNumber: 1,
Context: base.BasicIssueContext(1),
})
assert.NoError(t, err)
assertCommentsEqual(t, []*base.Comment{
Expand Down
18 changes: 3 additions & 15 deletions modules/migrations/migrate.go
Original file line number Diff line number Diff line change
Expand Up @@ -318,7 +318,7 @@ func migrateRepository(downloader base.Downloader, uploader base.Uploader, opts
for _, issue := range issues {
log.Trace("migrating issue %d's comments", issue.Number)
comments, _, err := downloader.GetComments(base.GetCommentOptions{
IssueNumber: issue.Number,
Context: issue.Context,
})
if err != nil {
if !base.IsErrNotSupported(err) {
Expand Down Expand Up @@ -376,7 +376,7 @@ func migrateRepository(downloader base.Downloader, uploader base.Uploader, opts
for _, pr := range prs {
log.Trace("migrating pull request %d's comments", pr.Number)
comments, _, err := downloader.GetComments(base.GetCommentOptions{
IssueNumber: pr.Number,
Context: pr.Context,
})
if err != nil {
if !base.IsErrNotSupported(err) {
Expand Down Expand Up @@ -404,26 +404,14 @@ func migrateRepository(downloader base.Downloader, uploader base.Uploader, opts
// migrate reviews
var allReviews = make([]*base.Review, 0, reviewBatchSize)
for _, pr := range prs {
number := pr.Number

// on gitlab migrations pull number change
if pr.OriginalNumber > 0 {
number = pr.OriginalNumber
}

reviews, err := downloader.GetReviews(number)
reviews, err := downloader.GetReviews(pr.Context)
if err != nil {
if !base.IsErrNotSupported(err) {
return err
}
log.Warn("migrating reviews is not supported, ignored")
break
}
if pr.OriginalNumber > 0 {
for i := range reviews {
reviews[i].IssueIndex = pr.Number
}
}

allReviews = append(allReviews, reviews...)

Expand Down
619 changes: 619 additions & 0 deletions modules/migrations/onedev.go

Large diffs are not rendered by default.

169 changes: 169 additions & 0 deletions modules/migrations/onedev_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
// 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"
"fmt"
"net/http"
"net/url"
"testing"
"time"

"code.gitea.io/gitea/modules/migrations/base"

"github.com/stretchr/testify/assert"
)

func TestOneDevDownloadRepo(t *testing.T) {
resp, err := http.Get("https://code.onedev.io/projects/go-gitea-test_repo")
if err != nil || resp.StatusCode != 200 {
t.Skipf("Can't access test repo, skipping %s", t.Name())
}

u, _ := url.Parse("https://code.onedev.io")
downloader := NewOneDevDownloader(context.Background(), u, "", "", "go-gitea-test_repo")
if err != nil {
t.Fatal(fmt.Sprintf("NewOneDevDownloader is nil: %v", err))
}
repo, err := downloader.GetRepoInfo()
assert.NoError(t, err)
assert.EqualValues(t, &base.Repository{
Name: "go-gitea-test_repo",
Owner: "",
Description: "Test repository for testing migration from OneDev to gitea",
CloneURL: "https://code.onedev.io/go-gitea-test_repo",
OriginalURL: "https://code.onedev.io/projects/go-gitea-test_repo",
}, repo)

milestones, err := downloader.GetMilestones()
assert.NoError(t, err)
assert.Len(t, milestones, 2)
deadline := time.Unix(1620086400, 0)
assert.EqualValues(t, []*base.Milestone{
{
Title: "1.0.0",
Deadline: &deadline,
Closed: &deadline,
},
{
Title: "1.1.0",
Description: "next things?",
},
}, milestones)

labels, err := downloader.GetLabels()
assert.NoError(t, err)
assert.Len(t, labels, 6)

issues, isEnd, err := downloader.GetIssues(1, 2)
assert.NoError(t, err)
assert.Len(t, issues, 2)
assert.False(t, isEnd)
assert.EqualValues(t, []*base.Issue{
{
Number: 4,
Title: "Hi there",
Content: "an issue not assigned to a milestone",
PosterName: "User 336",
State: "open",
Created: time.Unix(1628549776, 734000000),
Updated: time.Unix(1628549776, 734000000),
Labels: []*base.Label{
{
Name: "Improvement",
},
},
Context: onedevIssueContext{
foreignID: 398,
localID: 4,
IsPullRequest: false,
},
},
{
Number: 3,
Title: "Add an awesome feature",
Content: "just another issue to test against",
PosterName: "User 336",
State: "open",
Milestone: "1.1.0",
Created: time.Unix(1628549749, 878000000),
Updated: time.Unix(1628549749, 878000000),
Labels: []*base.Label{
{
Name: "New Feature",
},
},
Context: onedevIssueContext{
foreignID: 397,
localID: 3,
IsPullRequest: false,
},
},
}, issues)

comments, _, err := downloader.GetComments(base.GetCommentOptions{
Context: onedevIssueContext{
foreignID: 398,
localID: 4,
IsPullRequest: false,
},
})
assert.NoError(t, err)
assert.Len(t, comments, 1)
assert.EqualValues(t, []*base.Comment{
{
IssueIndex: 4,
PosterName: "User 336",
Created: time.Unix(1628549791, 128000000),
Updated: time.Unix(1628549791, 128000000),
Content: "it has a comment\r\n\r\nEDIT: that got edited",
},
}, comments)

prs, _, err := downloader.GetPullRequests(1, 1)
assert.NoError(t, err)
assert.Len(t, prs, 1)
assert.EqualValues(t, []*base.PullRequest{
{
Number: 5,
Title: "Pull to add a new file",
Content: "just do some git stuff",
PosterName: "User 336",
State: "open",
Created: time.Unix(1628550076, 25000000),
Updated: time.Unix(1628550076, 25000000),
Head: base.PullRequestBranch{
Ref: "branch-for-a-pull",
SHA: "343deffe3526b9bc84e873743ff7f6e6d8b827c0",
RepoName: "go-gitea-test_repo",
},
Base: base.PullRequestBranch{
Ref: "master",
SHA: "f32b0a9dfd09a60f616f29158f772cedd89942d2",
RepoName: "go-gitea-test_repo",
},
Context: onedevIssueContext{
foreignID: 186,
localID: 5,
IsPullRequest: true,
},
},
}, prs)

rvs, err := downloader.GetReviews(onedevIssueContext{
foreignID: 186,
localID: 5,
})
assert.NoError(t, err)
assert.Len(t, rvs, 1)
assert.EqualValues(t, []*base.Review{
{
IssueIndex: 5,
ReviewerName: "User 317",
State: "PENDING",
},
}, rvs)
}
10 changes: 7 additions & 3 deletions modules/migrations/restore.go
Original file line number Diff line number Diff line change
Expand Up @@ -208,13 +208,16 @@ func (r *RepositoryRestorer) GetIssues(page, perPage int) ([]*base.Issue, bool,
if err != nil {
return nil, false, err
}
for _, issue := range issues {
issue.Context = base.BasicIssueContext(issue.Number)
}
return issues, true, nil
}

// GetComments returns comments according issueNumber
func (r *RepositoryRestorer) GetComments(opts base.GetCommentOptions) ([]*base.Comment, bool, error) {
var comments = make([]*base.Comment, 0, 10)
p := filepath.Join(r.commentDir(), fmt.Sprintf("%d.yml", opts.IssueNumber))
p := filepath.Join(r.commentDir(), fmt.Sprintf("%d.yml", opts.Context.ForeignID()))
_, err := os.Stat(p)
if err != nil {
if os.IsNotExist(err) {
Expand Down Expand Up @@ -258,14 +261,15 @@ func (r *RepositoryRestorer) GetPullRequests(page, perPage int) ([]*base.PullReq
}
for _, pr := range pulls {
pr.PatchURL = "file://" + filepath.Join(r.baseDir, pr.PatchURL)
pr.Context = base.BasicIssueContext(pr.Number)
}
return pulls, true, nil
}

// GetReviews returns pull requests review
func (r *RepositoryRestorer) GetReviews(pullRequestNumber int64) ([]*base.Review, error) {
func (r *RepositoryRestorer) GetReviews(context base.IssueContext) ([]*base.Review, error) {
var reviews = make([]*base.Review, 0, 10)
p := filepath.Join(r.reviewDir(), fmt.Sprintf("%d.yml", pullRequestNumber))
p := filepath.Join(r.reviewDir(), fmt.Sprintf("%d.yml", context.ForeignID()))
_, err := os.Stat(p)
if err != nil {
if os.IsNotExist(err) {
Expand Down
4 changes: 4 additions & 0 deletions modules/structs/repo.go
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,7 @@ const (
GiteaService // 3 gitea service
GitlabService // 4 gitlab service
GogsService // 5 gogs service
OneDevService // 6 onedev service
)

// Name represents the service type's name
Expand All @@ -267,6 +268,8 @@ func (gt GitServiceType) Title() string {
return "GitLab"
case GogsService:
return "Gogs"
case OneDevService:
return "OneDev"
case PlainGitService:
return "Git"
}
Expand Down Expand Up @@ -322,5 +325,6 @@ var (
GitlabService,
GiteaService,
GogsService,
OneDevService,
}
)
1 change: 1 addition & 0 deletions options/locale/locale_en-US.ini
Original file line number Diff line number Diff line change
Expand Up @@ -904,6 +904,7 @@ migrate.git.description = Migrating or Mirroring git data from Git services
migrate.gitlab.description = Migrating data from GitLab.com or Self-Hosted gitlab server.
migrate.gitea.description = Migrating data from Gitea.com or Self-Hosted Gitea server.
migrate.gogs.description = Migrating data from notabug.org or other Self-Hosted Gogs server.
migrate.onedev.description = Migrating data from code.onedev.io or Self-Hosted OneDev server.
migrate.migrating_git = Migrating Git Data
migrate.migrating_topics = Migrating Topics
migrate.migrating_milestones = Migrating Milestones
Expand Down
1 change: 1 addition & 0 deletions public/img/svg/gitea-onedev.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
117 changes: 117 additions & 0 deletions templates/repo/migrate/onedev.tmpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
{{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" .}}

<div id="migrate_items">
<div class="inline field">
<label>{{.i18n.Tr "repo.migrate_items"}}</label>
<div class="ui checkbox">
<input name="milestones" type="checkbox" {{if .milestones}}checked{{end}}>
<label>{{.i18n.Tr "repo.migrate_items_milestones" | Safe}}</label>
</div>
<div class="ui checkbox">
<input name="labels" type="checkbox" {{if .labels}}checked{{end}}>
<label>{{.i18n.Tr "repo.migrate_items_labels" | Safe}}</label>
</div>
</div>
<div class="inline field">
<label></label>
<div class="ui checkbox">
<input name="issues" type="checkbox" {{if .issues}}checked{{end}}>
<label>{{.i18n.Tr "repo.migrate_items_issues" | Safe}}</label>
</div>
<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>
</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" .}}
42 changes: 42 additions & 0 deletions web_src/svg/gitea-onedev.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.